Professional Documents
Culture Documents
数据科学 Pandas数据分析讲义
数据科学 Pandas数据分析讲义
数据科学 Pandas数据分析讲义
学习目标
知道数据科学相关岗位和技能要求
1 数据科学的概念
产生背景:随着移动互联网的普及,人类社会产生的可收集数据呈指数级增长。日常使用互联网时,会产生许多数据
浏览的网页
在网页上逗留的时间
点击的链接
在社交网站上发表的内容
与之进行互动的人或组织
点赞的内容
大数据时代,数据科学应运而生,将深刻地改变企业的决策方式
数据量级不断增长,数据维度不断增加,数据类型日益复杂
新的业务模式成为可能,传统的数据处理工具捉襟见肘
需要新的方法来解决问题,以便更好地从海量数据中获取洞察力,指导决策
on
on
数据科学概念定义
如今许多产品或服务中都有数据科学的身影,例如广告推荐、电影票房预测、潜在客户寻找,个性化推送等。
“数据科学”一词在20世纪60年代~80年代间出现在计算机科学文献中,20世纪90年代后期,这个词才开始时常出现在统计和数据
挖掘领域。
2001年,数据科学成为独立学科,横跨计算机科学、统计学、数学、软件工程等多个领域,从定义与解决实际问题出发,经过描
th
th
述、发现、预测、建议四个环节,从数据中获得洞察力,从而解决问题。
数据科学是将数据转化为行动的艺术,这种转化主要通过开发数据产品来完成。数据产品可以提供
金融工具的买/卖策略
Py
提高产品收益率的措施
改进产品营销的步骤等,而无须将底层数据暴露给决策者
Py
数据产品能回答以下问题:
应该对哪些产品进行更多的广告宣传来提高利润?
如何在降低成本的同时改进合规计划?
员
员
采用什么制造工艺才能实现一个更好的产品?而回答这些问题的关键在于,了解我们拥有的数据以及归纳这些数据中包含的
信息。
数据分析和数据工程
为了高效探索数据中的价值,我们需要数据分析技术和数据工程的配合
序
序
数据分析是对特定的数据进行分析和洞察的行为
数据工程是指利用各种工具、方法或系统,高效探索和转化数据商业价值的工程化技术
业务数据化,其实就是业务的数据工程化,即通过对业务数据进行收集、整理、分析,实现对业务更深入的理解,并最终实
现业务的持续优化。
程
程
应用数据科学的最简单的例子是搜索引擎,它将用户在搜索中的交互行为数据化,然后根据用户停留时长、点击次数等条件
优化搜索结果的展示效果,提升用户搜索体验,吸引更多的用户使用,进而产生更多的数据用于优化。这是一个数据闭环,
能够实现持续的业务优化。
马
马
黑
数据科学的工作流程
1.定义问题。
2.获取训练和测试数据。
3.数据准备、清洗。
4.分析,识别模式,探索数据。
5.建立模型,预测问题,解决问题。
6.形成可视化报告,呈现问题解决步骤,找到解决方案。
7.提供或提交结果
数据科学能干什么?交付成果举例
分类(如判断是否是垃圾邮件)
推荐(如Amazon的商品推荐系统)
异常检测(如欺诈检测,刷单,异常流量)
识别(如人脸识别)
可实施的见解(如仪表板、报告等可视化工具)
自动化流程和决策(如信用卡核准)
评分和排名(如信用评分)
分群(如基于人口统计进行的营销)
预测(如销售和收入)
2 数据科学项目概述
相较于其他项目,数据科学项目的显著特点是,其经常会与数据处理的过程以及数据产品产生的过程形成耦合。
on
on
th
th
上图展示了一个典型的数据科学项目的流程,过程包括采集数据、整合数据、训练模型、部署模型等。
不同的阶段有不同的产出:有的阶段产出结构化的、可分析的数据集,有的则产出待优化的模型。
没有一个数据科学项目是能够一次性得出最佳模型的,因为数据处理的过程和挖掘数据含义的过程,就是不断尝试、不断校准的过
Py
程。
Py
数据获取
公司内部数据
员
员
首先应该评估公司内现有数据的相关性和质量。
数据可以存储在数据库、数据集市、数据仓库
数据仓库中的是经过预处理的数据,主要用于读取和分析
数据集市是数据仓库的一个子集,面向特定的业务部门提供服务
有时在公司内部查找数据也是一个挑战,因为随着公司业务的增长,数据会分散在许多地方。
序
序
外部数据
公司内部数据不足时,则需要寻找外部数据。
爬虫
程
三方公司购买
程
政府和组织提供免费数据
数据质量
跟业务方核对数据准确性,特别是人工录入数据
马
马
已有数据字段是否满足后续项目需求,如果数据字段不够,需要埋点开发
数据准备
数据准备是指对获取到的数据进行清洗、整合,并将其由原始表单转换为可在模型中直接使用的数据的过程。在数据准备过程中会检
测并纠正数据中的各种错误,合并来自不同数据源的数据,然后进行转换。、
黑
1.数据清洗:数据清洗专注于消除数据中的错误,错误通常有以下两种类型:
解释错误,如年龄大于150岁
数据不一致,即数据源之间不一致或标准化值不一致。例如,当表示性别为“女”时,在一张表中使用“Female”表示,而在另一张
表中使用“F”表示。
2.数据整合:对来自不同数据源的数据进行整合
项目使用的数据来自多个不同的源头,因此数据的大小、类型和结构可能各不相同。
关联(JOIN),即通过一个表中的信息丰富另一个表中的信息。
追加(APPENDING),即将一个表中的信息添加到另一个表中。
3.数据转换
某些模型要求数据具有某种格式。我们已经清洗并整合了数据,接下来我们将执行下一个任务:转换数据,生成符合模型要求的
数据格式。
大部分数据分析项目要求我们将数据集加工成“宽表”,即按样本组织数据,使一个样本占据一行,每行又由若干列组成,每列数
据被标识为该样本的一个指标(或属性、特征)
数据转换就是将宽表加工出来的过程。
数据可视化
数据可视化是指将相对晦涩的数据通过可视的、交互的方式进行展示,从而形象直观地表达数据中蕴含的信息和规律的方法。
数据科学中的可视化要求更加灵活地将晦涩的模型结果以及算法结果展现出来,提供给数据使用者,在数据科学场景中,自助可视化
分析(Self-Service Analysis)越来越成为一种趋势。
各种BI工具如power BI,Tableau,FineBI等 都是常用的自助可视化分析工具,当然也可以使用Python,或者前端代码实现数据可视
化
数据探索
数据探索:是指通过各种方法对数据集进行探索,研究各变量的分布以及变量之间的关系等。
包括使用可视化技术(如简单的线图、直方图,复杂的 Sankey和网络图等)来了解变量
包括通过写代码完成机器学习模型的训练、机器学习模型的调优等工作。
创建模型
复杂算法模型(逻辑回归,聚类,线性回归....)
简单逻辑模型RFM,AARRR... ...
Python可以完成上面介绍的数据科学项目的所有阶段
部署上线
数据科学项目的成果可通过数据报表、模型及数据应用等形式来展现
数据报表
数据应用
3 数据驱动运营
数据分析/数据科学在企业落地的 几个阶段
on
on
th
th
Py
Py
员
员
4 数据科学相关岗位
序
序
数据科学领域的三种核心技能
领域知识:对特定商业领域具有专业认知,例如风控、金融获客、选址等。
编程技能:掌握计算机科学的相关知识,对数据产品环境具有构造能力。
建模技能:掌握应用数学、统计学等相关知识,能够提供检测数据科学问题的理论框架。
程
数据科学领域相关岗位
程
数据科学家(DS)
具备三种数据科学核心技能(领域知识、编程技能、建模技能)且表现都很出色
比较少,一般title比较高
马
马
数据分析师 (DA)
初级数据科学家,不需要有很深的数学功底和算法研究能力
必须掌握基本的统计学知识和数据处理工具(例如Excel、SQL、Python、Tableau 等)的用法
黑
具备基本的数据可视化能力
具备与业务相关的专业知识,比如金融风控、气象、游戏App运营等方面的知识
需要具有良好的沟通能力,不仅与业务方交流业务需求,还要和数据团队的其他成员良好协作
解决问题时,运用基本的数学分析能力,用线性模型进行回归分析,或者使用Excel/SQL/Python 处理数据,制作可视化图
表P,最后进行结果汇报(数据分析报告),从而使上级能够清晰了解数据的具体情况和重要性对比。
数据分析师需要掌握以下几个方面的知识:编程(Python/R)、数据清洗、数据可视化、基本统计学原理、常见机器学习算法
数据分析师主要工作:访问和查询不同数据源,处理和清洗数据,汇总数据,进行数据可视化并生成报告
数据工程师(DE):负责搭建数据工程所需的技术平台(包括数据连接器、数据存储和计算引擎、工作流引擎等),实现所需的
功能(数据可视化),保证数据处理的稳定可靠,为架构师、数据科学家和数据分析师提供工作支持。 数据工程师需要具备的能
力:
技术能力:包括编程能力、架构设计能力、工程能力
业务能力:主要指对于业务的理解能力。
实战能力:包括数据洞察能力、文档撰写能力。
团队协作能力:包括学习辅导能力、沟通能力、合作能力
数仓工程师就属于数据工程师
数据运营,数据产品经理
数据运营,属于纯业务岗,负责运营工作,但对数据处理分析能力有要求
数据产品经理,产品经理岗,狭义上讲属于做数据产品的产品经理(数据质量产品——数据质量管理平台,数据工具产品
——大数据分析平台)
5 数据科学相关岗位招聘要求
on
on
th
th
Py
Py
员
员
序
序
程
程
马
马
黑
6 阶段课程设计
Pandas
培养数据操纵能力,和数据处理思路
可以使用Pandas对数据做任意操作(切片,分组,聚合....)
拿到一份数据,知道可以分析出什么东西来
机器学习
介绍常用机器学习算法原理
主要讲算法在数据分析中应用,不涉及复杂数学推导
风控项目
一个完整的数据科学项目
介绍从数据处理到模型上线的全部流程
将Pandas和算法结合起来解决实际问题
数仓
稍大一些的互联网公司数据都保存在大数据仓库中(hadoop+hive)
主要掌握Hive SQL查询语句
数仓的基本建模理论
推荐算法
数据挖掘项目
介绍时下比较火的推荐算法原理
常用召回排序算法
01 Python数据分析简介
学习目标
on
on
了解Python做数据分析的优势
知道Python数据分析常用开源库
1 为什么使用Python进行数据分析
th
th
1.1 使用Python进行数据分析的优势
Py
Py
员
员
序
序
程
程
Python作为当下最为流行的编程语言之一,可以独立完成数据分析的各种任务
马
马
功能强大,在数据分析领域里有海量开源库,并持续更新
是当下热点——机器学习/深度学习 领域最热门的编程语言
除数据分析领域外,在爬虫,Web开发等领域均有应用
黑
与Excel,PowerBI,Tableau等软件比较
Excel有百万行数据限制,PowerBI ,Tableau在处理大数据的时候速度相对较慢
Python作为热门编程语言,功能远比Excel,PowerBI,Tableau等软件强大
Python跨平台,Windows,MacOS,Linux都可以运行
与R语言比较
Python在处理海量数据的时候比R语言效率更高
Python的工程化能力更强,应用领域更广泛,R专注于统计与数据分析领域
Python在非结构化数据(文本,音视频,图像)和深度学习领域比R更具有优势
在数据分析相关开源社区,python相关的内容远多于R语言
2 常用Python数据分析开源库介绍
2.1 Numpy
是一个运行速度非常快的数学库,主要用于数组计算,包含:
一个强大的N维数组对象 ndarray
广播功能函数
整合 C/C++/Fortran 代码的工具
线性代数、傅里叶变换、随机数生成等功能
2.2 Pandas
Pandas是一个强大的分析结构化数据的工具集
它的使用基础是Numpy(提供高性能的矩阵运算)
用于数据挖掘和数据分析,同时也提供数据清洗功能
on
on
Pandas利器之 Series
它是一种类似于一维数组的对象
是由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成
仅由一组数据也可产生简单的Series对象
Pandas利器之 DataFrame
th
th
DataFrame是Pandas中的一个表格型的数据结构
包含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型等)
DataFrame即有行索引也有列索引,可以被看做是由Series组成的字典
Py
Py
2.3 Matplotlib
Matplotlib 是一个功能强大的数据可视化开源Python库
员
员
Python中使用最多的图形绘图库
可以创建静态, 动态和交互式的图表
2.4 Seaborn
序
序
Seaborn是一个Python数据可视化开源库
建立在matplotlib之上,并集成了pandas的数据结构
Seaborn通过更简洁的API来绘制信息更丰富,更具吸引力的图像
面向数据集的API,与Pandas配合使用起来比直接使用Matplotlib更方便
程
程
2.5 Sklearn
马
马
scikit-learn 是基于 Python 语言的机器学习工具
简单高效的数据挖掘和数据分析工具
可供大家在各种环境中重复使用
建立在 NumPy ,SciPy 和 matplotlib 上
黑
数据清理和转换
数值模拟
统计分析
数据可视化
机器学习等
Jupyter Notebook是数据分析学习和开发的首选开发环境
小结
了解Python做数据分析的优势
Python可以独立高效的完成数据分析相关的全部工作
知道Python数据分析常用开源库
Pandas,Numpy,Matplotlib,Seaborn,SKlearn,Jupyter Notebook
02_Python数据分析开发环境搭建
学习目标
独立完成开发环境搭建
掌握 Anaconda的使用方法
掌握 Jupyter Notebook的使用方法
1 开发环境搭建
1.1 简介
Anaconda 是最流行的数据分析平台,全球两千多万人在使用
Anaconda 附带了一大批常用数据科学包
conda
Python
150 多个科学包及其依赖项
Anaconda 是在 conda(一个包管理器和环境管理器)上发展出来的
on
on
Conda可以帮助你在计算机上安装和管理数据分析相关包
Anaconda的仓库中包含了7000多个数据科学相关的开元库
Anaconda 包含了虚拟环境管理工具
通过虚拟环境可以使不同的Python或者开元库的版本同时存在
th
th
1.2 Anaconda安装
Anaconda 可用于多个平台( Windows、Mac OS X 和 Linux)
可以在官网上下载对应平台的安装包
Py
如果计算机上已经安装了 Python,安装不会对你有任何影响
安装的过程很简单,一路下一步即可
Py
2 Anaconda的使用
员
员
2.1 Anaconda的界面
安装好Anaconda后点击图标可以打开Anaconda的管理面板
序
序
程
程
马
马
黑
2.2 Anaconda的包管理功能
点击Environment选项卡,进入到环境管理界面
可以通过管理界面安装
on
on
可以通过conda install 安装
th
可以通过pip install 安装
安装其他包速度慢可以指定国内镜像
Py
# 阿里云:https://mirrors.aliyun.com/pypi/simple/
# 豆瓣:https://pypi.douban.com/simple/
# 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
员
员
# 中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/
序
2.3 Anaconda的虚拟环境管理
虚拟环境的作用
程
很多开源库版本升级后API有变化,老版本的代码不能在新版本中运行
程
将不同Python版本/相同开源库的不同版本隔离
不同版本的代码在不同的虚拟环境中运行
通过Anaconda界面创建虚拟环境
马
马
黑
通过命令行创建虚拟环境
conda create -n 虚拟环境名字 python=python版本 #创建虚拟环境
conda activate 虚拟环境名字 #进入虚拟环境
conda deactivate 虚拟环境名字 #退出虚拟环境
conda remove -n 虚拟环境名字 --all #删除虚拟环境
3 Jupyter Notebook的使用
on
on
th
th
Py
员
3.2 Jupyter Notebook使用简介
新建notebook文档
序
序
Jupyter Notebook 文档的扩展名为.ipynb
程
程
马
马
新建文件之后会打开Notebook界面
黑
Jupyter Notebook的cell
代码的输入框和输出显示的结果就是cell
菜单栏中相关按钮功能介绍:
常用快捷键
两种模式通用快捷键
Shift+Enter ,执行本单元代码,并跳转到下一单元
Ctrl+Enter ,执行本单元代码,留在本单元
on
on
cell行号前的 * ,表示 代码正在运行
命令模式
:按ESC进入
th
th
Y ,cell切换到Code模式
M ,cell切换到Markdown模式
A ,在当前cell的上面添加cell
B ,在当前cell的下面添加cell
Py
编辑模式
双击D :删除当前cell
Py
:按Enter进入
多光标操作: Ctrl键点击鼠标 (Mac:CMD+点击鼠标)
回退: Ctrl+Z (Mac:CMD+Z)
员
员
重做: Ctrl+Y (Mac:CMD+Y)
补全代码:变量、方法后跟 Tab键
为一行或多行代码添加/取消注释: Ctrl+/ (Mac:CMD+/)
Jupyter notebook的功能扩展
序
序
安装jupyter_contrib_nbextensions库
#进入到虚拟环境中
程
马
安装结束后启动jupyter notebook
黑
配置扩展功能
使用Markdown语法可以在代码间穿插格式化的文本作为说明文字或笔记
Markdown语法简介
on
on
掌握标题和缩进
th
th
Py
Py
员
员
序
序
小结
程
独立完成开发环境搭建
程
安装Anaconda作为开发环境的管理器
掌握 Anaconda 的使用方法
马
马
Anaconda可以管理虚拟环境
Anaconda可以管理虚拟环境中的软件包
掌握 Jupyter Notebook 的使用方法
扩展名为.ipynb
黑
在cell中编辑代码和展示输出结果
支持Markdown语法
03_Pandas DataFrame 入门
学习目标
掌握DataFrame加载数据文件的方法
知道如何加载部分数据
知道如何对数据进行简单的分组聚合操作
1 Pandas DataFrame简介
Pandas是用于数据分析的开源Python库,可以实现数据加载,清洗,转换,统计处理,可视化等功能
DataFrame和Series是Pandas最基本的两种数据结构
DataFrame用来处理结构化数据(SQL数据表,Excel表格)
Series用来处理单列数据,也可以把DataFrame看作由Series对象组成的字典或集合
2 加载数据集
做数据分析首先要加载数据,并查看其结构和内容,对数据有初步的了解
查看行,列
查看每一列中存储信息的类型
Pandas 并不是 Python 标准库,所以先导入Pandas
import Pandas as pd
导入Pandas库之后,通过read_csv加载文件
加载CSV文件
df = pd.read_csv('data/movie.csv') # 加载movie.csv文件
df.head() # 展示前5条数据
加载TSV文件
on
on
tsv文件 Tab-Separated Values
th
输出结果
0
1
Afghanistan
Afghanistan
Asia
Asia
1952
1957
28.801
30.332
Py
8425333
9240934
779.445314
820.853030
2 Afghanistan Asia 1962 31.997 10267083 853.100710
3 Afghanistan Asia 1967 34.020 11537966 836.197138
4 Afghanistan Asia 1972 36.088 13079460 739.981106
... ... ... ... ... ... ...
员
员
1699 Zimbabwe Africa 1987 62.351 9216418 706.157306
1700 Zimbabwe Africa 1992 60.377 10704340 693.420786
1701 Zimbabwe Africa 1997 46.809 11404948 792.449960
1702 Zimbabwe Africa 2002 39.989 11926563 672.038623
序
序
1703 Zimbabwe Africa 2007 43.487 12311143 469.709298
可以通过Python的内置函数type查看返回的数据类型
程
程
type(df)
输出结果
马
马
pandas.core.frame.DataFrame
每个dataframe都有一个shape属性,可以获取DataFrame的行数,列数
黑
df.shape
输出结果
(1704, 6)
可以通过DataFrame的columns属性 获取DataFrame中的列名
df.columns
输出结果
如何获取每一列的数据类型?
与SQL中的数据表类似,DataFrame中的每一列的数据类型必须相同,不同列的数据类型可以不同
可以通过dtypes属性,或者info()方法获取数据类型
df.dtypes
输出结果
country object
continent object
year int64
lifeExp float64
pop int64
gdpPercap float64
dtype: object
df.info()
输出结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1704 entries, 0 to 1703
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 country 1704 non-null object
1 continent 1704 non-null object
2 year 1704 non-null int64
3 lifeExp 1704 non-null float64
4 pop 1704 non-null int64
on
on
5 gdpPercap 1704 non-null float64
dtypes: float64(2), int64(2), object(2)
memory usage: 80.0+ KB
Pandas与Python常用数据类型对照
th
th
Pandas类型 Python类型 说明
int64 int 整形
Py
float64 float
Py
浮点型
3 查看部分数据
员
员
3.1 根据列名加载部分列数据
序
序
加载一列数据,通过df['列名']方式获取
country_df = df['country']
#获取数据前5行
country_df.head()
程
程
输出结果
0 Afghanistan
马
马
1 Afghanistan
2 Afghanistan
3 Afghanistan
4 Afghanistan
黑
通过列名加载多列数据,通过df[['列名1','列名2',...]]
subset = df[['country','continent','year']]
#打印后五行数据
print(subset.tail())
输出结果
3.2 按行加载部分数据
loc:通过行索引标签获取指定行数据
#先打印前5行数据 观察第一列
print(df.head())
显示结果
上述结果中发现,最左边一列是行号,这一列没有列名的数据是DataFrame的行索引,Pandas会使用行号作为默认的行索引。
我们可以使用 .loc 方法传入行索引,来获取DataFrame的部分数据(一行,或多行)
# 获取第一行数据,并打印
print(df.loc[0])
显示结果
country Afghanistan
continent Asia
year 1952
on
on
lifeExp 28.801
pop 8425333
gdpPercap 779.445
Name: 0, dtype: object
#获取第100行数据,并打印
th
th
print(df.loc[99])
显示结果
Py
country
continent
Bangladesh
Asia
Py
year 1967
lifeExp 43.453
pop 62821884
员
员
gdpPercap 721.186
Name: 99, dtype: object
序
number_of_rows = df.shape[0]
# 总行数-1 获取最后一行行索引
last_row_index = number_of_rows - 1
# 获取最后一行数据,并打印
print(df.loc[last_row_index])
程
程
显示结果
country Zimbabwe
马
马
continent Africa
year 2007
lifeExp 43.487
pop 12311143
黑
gdpPercap 469.709
Name: 1703, dtype: object
使用tail方法获取最后一行数据
输出结果
我们可以打印两种结果的类型
subset_loc = df.loc[0]
subset_head = df.head(n=1)
print(type(subset_loc))
print(type(subset_head))
输出结果
<class 'pandas.core.series.Series'>
<class ’pandas.core.frame.DataFrame’>
loc:通过索引标签获取指定多行数据
输出结果
iloc : 通过行号获取行数据
在当前案例中,使用iloc 和 loc效果是一样的
需要注意的是,iloc传入的是索引的序号,loc是索引的标签
在当前案例中,索引标签和索引序号刚好相同
并不是所有情况下索引标签=索引序号
例如:在做时间序列分析的时候,我们可以使用日期作为行索引
此时索引标签日期
索引序号依然是0,1,2,3
on
on
# 获取第一行数据,并打印
print(df.iloc[0])
输出结果
th
th
country Afghanistan continent Asia year 1952 lifeExp 28.801 pop 8425333 gdpPercap 779.445 Name: 0, dtype: object
#获取第100行数据,并打印
print(df.iloc[99])
Py
Py
输出结果
country Bangladesh continent Asia year 1967 lifeExp 43.453 pop 62821884 gdpPercap 721.186 Name: 99, dtype: object
员
员
# 获取最后一行 通过shape 获取一共有多少行
number_of_rows = df.shape[0]
# 总行数-1 获取最后一行行索引
last_row_index = number_of_rows - 1
# 获取最后一行数据,并打印
序
序
print(df.iloc[[0, 99, 999]])
输出结果
程
0 Afghanistan Asia 1952 28.801 8425333 779.445314 99 Bangladesh Asia 1967 43.453 62821884 721.186086 999
Mongolia Asia 1967 51.253 1149500 1226.041130
使用iloc时传入-1可以获取最后一行数据
马
马
print(df.iloc[-1])
输出结果
黑
country Zimbabwe continent Africa year 2007 lifeExp 43.487 pop 12311143 gdpPercap 469.709 Name: 1703, dtype:
object
注意 使用iloc时可以传入-1来获取最后一行数据,使用loc的时候不行
Pandas V0.20 开始不再支持使用 ix 获取数据
可以把ix看作loc 和 iloc的结合,因为它允许通过标签或者整数取子集
默认情况下,它会搜索标签,如果找不到相应的标签,就会改用整数索引,这可能导致混乱
使用ix时与使用loc或iloc时的代码完全相同,只是将loc或者iloc换成ix
# 一下代码只能在pandas 版本低于0.20的时候才能成功运行
df.ix[0] #获取第一行数据
df.ix[99] # 获取第100行数据
df.ix[[0,99,999]] # 获取第1行,第100行,第1000行数据
3.3 获取指定行/列数据
loc和iloc属性既可以用于获取列数据,也可以用于获取行数据
df.loc[[行],[列]]
df.iloc[[行],[列]]
使用 loc 获取数据中的1列/几列
df.loc[[所有行],[列名]]
取出所有行,可以使用切片语法 df.loc[ : , [列名]]
subset = df.loc[:,['year','pop']]
print(subset.head())
输出结果
year pop
0 1952 8425333
1 1957 9240934
2 1962 10267083
3 1967 11537966
4 1972 13079460
使用 iloc 获取数据中的1列/几列
df.iloc[:,[列序号]] # 列序号可以使用-1代表最后一列
subset = df.iloc[:,[2,4,-1]]
print(subset.head())
输出结果
on
2 1962 10267083 853.100710
3 1967 11537966 836.197138
4 1972 13079460 739.981106
th
subset = df.loc[:,[2,4,-1]]
print(subset.head())
Py
输出结果
Py
KeyError: "None of [Int64Index([2, 4, -1], dtype='int64')] are in the [columns]"
iloc只能接受行/列的索引,不能传入行名,或者列名
员
员
subset = df.loc[:,[2,4,-1]]
print(subset.head())
序
序
输出结果
马
输出结果
[0, 1, 2, 3, 4]
黑
subset = df.iloc[:,tmp_range]
print(subset.head())
输出结果
tmp_range = list(range(3,5))
print(tmp_range)
输出结果
[3, 4]
subset = df.iloc[:,tmp_range]
print(subset.head())
输出结果
lifeExp pop
0 28.801 8425333
1 30.332 9240934
2 31.997 10267083
3 34.020 11537966
4 36.088 13079460
在 iloc中使用切片语法获取几列数据
使用切片语法获取前三列
subset = df.iloc[:,3:6]
print(subset.head())
输出结果
on
获取第0,2,4列
subset = df.iloc[:,0:6:2]
print(subset.head())
th
th
输出结果
2
3
Afghanistan
Afghanistan
1962
1967
10267083
11537966
Py
4 Afghanistan 1972 13079460
使用 loc/iloc 获取指定行,指定列的数据
员
员
使用loc
print(df.loc[42,'country'])
序
序
输出结果
Angola
程
使用iloc
程
print(df.iloc[42,0])
马
马
输出结果
Angola
黑
不要混淆loc和iloc,df.loc[42,0] 会报错
print(df.loc[42,0])
输出结果
获取多行多列
可以把获取单行单列的语法和获取多行多列的语法结合起来使用
获取 第一列,第四列,第六列(country,lifeExp,gdpPercap) 数据中的第1行,第100行和第1000行
print(df.iloc[[0,99,999],[0,3,5]])
输出结果
在实际工作中,获取某几列数据的时候,建议传入实际的列名,使用列名的好处:
增加代码的可读性
避免因列顺序的变化导致取出错误的列数据
print(df.loc[[0,99,999],['country','lifeExp','gdpPercap']])
输出结果
print(df.loc[2:6,['country','lifeExp','gdpPercap']])
输出结果
on
4 分组和聚合计算
在我们使用Excel或者SQL进行数据处理时,Excel和SQL都提供了基本的统计计算功能
当我们再次查看gapminder数据的时候,可以根据数据提出几个问题
th
th
print(df.head(10))
输出结果
Py
Py
country continent year lifeExp pop gdpPercap
0 Afghanistan Asia 1952 28.801 8425333 779.445314
1 Afghanistan Asia 1957 30.332 9240934 820.853030
员
员
2 Afghanistan Asia 1962 31.997 10267083 853.100710
3 Afghanistan Asia 1967 34.020 11537966 836.197138
4 Afghanistan Asia 1972 36.088 13079460 739.981106
5 Afghanistan Asia 1977 38.438 14880372 786.113360
6 Afghanistan Asia 1982 39.854 12881816 978.011439
序
序
7 Afghanistan Asia 1987 40.822 13867957 852.395945
8 Afghanistan Asia 1992 41.674 16317921 649.341395
9 Afghanistan Asia 1997 41.763 22227415 635.341351
① 每一年的平均预期寿命是多少?每一年的平均人口和平均GDP是多少?
程
② 如果我们按照大洲来计算,每年个大洲的平均预期寿命,平均人口,平均GDP情况又如何?
程
③ 在数据中,每个大洲列出了多少个国家和地区?
4.1 分组方式
马
对于上面提出的问题,需要进行分组-聚合计算
先将数据分组(每一年的平均预期寿命问题 按照年份将相同年份的数据分成一组)
对每组的数据再去进行统计计算如,求平均,求每组数据条目数(频数)等
黑
再将每一组计算的结果合并起来
可以使用DataFrame的groupby方法完成分组/聚合计算
print(df.groupby('year')['lifeExp'].mean())
显示结果
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
我们将上面一行代码拆开,逐步分析
通过df.groupby('year')先创一个分组对象,如果打印这个分组的DataFrame,会返回一个内存地址
grouped_year_df = df.groupby('year')
print(type(grouped_year_df))
print(grouped_year_df)
显示结果
<class 'pandas.core.groupby.generic.DataFrameGroupBy'>
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x123493f10>
我们可以从分组之后的数据DataFrameGroupBy中,传入列名获取我们感兴趣的数据,并进行进一步计算
grouped_year_df_lifeExp = grouped_year_df['lifeExp']
print(type(grouped_year_df_lifeExp))
print(grouped_year_df_lifeExp)
显示结果
<class 'pandas.core.groupby.generic.SeriesGroupBy'>
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001E1938D0710>
on
对分组后的数据计算平均值
mean_lifeExp_by_year = grouped_year_df_lifeExp.mean()
print(mean_lifeExp_by_year)
显示结果
th
th
year
1952 49.057620
1957 51.507401
Py
1962
1967
53.609249
55.678290
Py
1972 57.647386
1977 59.570157
1982 61.533197
员
员
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
序
序
Name: lifeExp, dtype: float64
print(df.groupby(['year', 'continent'])[['lifeExp','gdpPercap']].mean())
程
显示结果
马
马
lifeExp gdpPercap
year continent
1952 Africa 39.135500 1252.572466
Americas 53.279840 4079.062552
Asia 46.314394 5195.484004
黑
on
2007 Africa 54.806038 3089.032605
Americas 73.608120 11003.031625
Asia 70.728485 12473.026870
Europe 77.648600 25054.481636
Oceania 80.719500 29810.188275
th
th
上面的代码按年份和大洲对数据进行分组,针对每一组数据计算了对应的平均预期寿命 lifeExp 和 平均GDP
输出的结果中 year continent 和 lifeExp gdpPercap 不在同一行, year continent两个行索引存在层级结构,后面的章节会详细
介绍这种复合索引的用法
如果想去掉 year continent的层级结构,可以使用reset_index方法(重置行索引)
Py
Py
multi_group_var = df.groupby(['year', 'continent'])[['lifeExp','gdpPercap']].mean()
flat = multi_group_var.reset_index()
print(flat.head(15))
员
员
显示结果
序
1 1952 Americas 53.279840 4079.062552
2 1952 Asia 46.314394 5195.484004
3 1952 Europe 64.408500 5661.057435
4 1952 Oceania 69.255000 10298.085650
5 1957 Africa 41.266346 1385.236062
程
6
7
1957
1957
Americas
Asia
55.960280
49.318544
4616.043733
5787.732940
程
8 1957 Europe 66.703067 6963.012816
9 1957 Oceania 70.295000 11598.522455
10 1962 Africa 43.319442 1598.078825
马
马
11 1962 Americas 58.398760 4901.541870
12 1962 Asia 51.563223 5729.369625
13 1962 Europe 68.539233 8365.486814
14 1962 Oceania 71.085000 12696.452430
黑
4.2 分组频数计算
在数据分析中,一个常见的任务是计算频数
df.groupby('continent')['country'].nunique()
显示结果
continent
Africa 52
Americas 25
Asia 33
Europe 30
Oceania 2
Name: country, dtype: int64
5 基本绘图
可视化在数据分析的每个步骤中都非常重要,在理解或清理数据时,可视化有助于识别数据中的趋势
global_yearly_life_expectancy = df.groupby('year')['lifeExp'].mean()
print(global_yearly_life_expectancy)
显示结果
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
可以通过plot画图
global_yearly_life_expectancy.plot()
on
on
显示结果
<matplotlib.axes._subplots.AxesSubplot at 0x1e196e73f98>
th
th
Py
Py
员
员
序
序
小结
本节课程介绍了如何使用Pandas的DataFrame加载数据,并介绍了如何对数据进行简单的分组聚合
程
pd.read_csv # 加载CSV文件
程
pd.loc # 从DataFrame中获取部分数据,传入索引名字
pd.iloc # 从DataFrame中获取部分数据,传入索引序号
pd.groupby # 分组
马
04_Pandas 数据结构
黑
学习目标
掌握Series的常用属性及方法
掌握DataFrame的常用属性及方法
掌握更改Series和DataFrame的方法
掌握如何导入导出数据
1 创建Series和DataFrame
DataFrame和Series是Pandas最基本的两种数据结构
1.1 创建Series
在Pandas中,Series是一维容器,Series表示DataFrame的每一列
可以把DataFrame看作由Series对象组成的字典,其中key是列名,值是Series
Series和Python中的列表非常相似,但是它的每个元素的数据类型必须相同
创建 Series 的最简单方法是传入一个Python列表,如果传入的数据类型不统一,最终的dtype通常是object
import pandas as pd
s = pd.Series(['banana',42])
print(s)
输出结果
0 banana
1 42
dtype: object
上面的结果中,左边显示的0,1是Series的索引
创建Series时,可以通过index参数 来指定行索引
输出结果
1.2 创建 DataFrame
可以使用字典来创建DataFrame
name_list = pd.DataFrame(
{'Name':['Tome','Bob'],
on
on
'Occupation':['Teacher','IT Engineer'],
'age':[28,36]})
print(name_list)
输出结果
th
th
Name Occupation age
0 Tome Teacher 28
1 Bob IT Engineer 36
Py
创建DataFrame的时候可以使用colums参数指定列的顺序,也可以使用index来指定行索引
Py
name_list = pd.DataFrame(data = {'Occupation':['Teacher','IT Engineer'],'Age':[28,36]},columns=
['Age','Occupation'],index=['Tome','Bob'])
print(name_list)
员
员
输出结果
Age Occupation
序
序
Tome 28 Teacher
Bob 36 IT Engineer
2 Series 常用操作
程
程
2.1 Series常用属性
使用 DataFrame的loc 属性获取数据集里的一行,就会得到一个Series对象
马
马
data = pd.read_csv('nobel_prizes.csv',index_col='id')
data.head()
黑
输出结果
id =
941 2017 physics NaN Rainer Weiss "for decisive contributions to the LIGO detect... 2
942 2017 physics NaN Barry C. Barish "for decisive contributions to the LIGO detect... 4
943 2017 physics NaN Kip S. Thorne "for decisive contributions to the LIGO detect... 4
944 2017 chemistry NaN Jacques Dubochet "for developing cryo-electron microscopy for t... 3
945 2017 chemistry NaN Joachim Frank "for developing cryo-electron microscopy for t... 3
使用行索引标签选择一条记录
first_row = data.loc[941]
type(first_row)
输出结果
pandas.core.series.Series
print(first_row)
输出结果
year 2017
category physics
overallMotivation NaN
firstname Rainer
surname Weiss
motivation "for decisive contributions to the LIGO detect...
share 2
Name: 941, dtype: object
first_row.index
输出结果
print(first_row.values)
输出结果
on
on
[2017 'physics' nan 'Rainer' 'Weiss'
'"for decisive contributions to the LIGO detector and the observation of gravitational waves"'
2]
Series的keys方法,作用个index属性一样
th
th
data.keys()
输出结果
Py
Py
Index(['year', 'category', 'overallMotivation', 'firstname', 'surname',
'motivation', 'share'],
dtype='object')
Series的一些属性
员
员
属性 说明
loc 使用索引值取子集
序
序
iloc 使用索引位置取子集
dtype或dtypes Series内容的类型
T Series的转置矩阵
程
程
shape 数据的维数
size Series中元素的数量
马
马
values Series的值
2.2 Series常用方法
针对数值型的Series,可以进行常见计算
黑
输出结果
1.982665222101842
share.max() # 计算最大值
输出结果
share.min() # 计算最小值
输出结果
1
share.std() # 计算标准差
输出结果
0.9324952202244597
通过value_counts()方法,可以返回不同值的条目数量
输出结果
0 James Cameron
1 Gore Verbinski
2 Sam Mendes
3 Christopher Nolan
4 Doug Walker
Name: director_name, dtype: object
actor_1_fb_likes.head() #查看主演的facebook点赞数
on
on
输出结果
0 1000.0
1 40000.0
th
th
2 11000.0
3 27000.0
4 131.0
Name: actor_1_facebook_likes, dtype: float64
Py
pd.set_option('max_rows', 8) # 设置最多显示8行
Py
director.value_counts() # 统计不同导演指导的电影数量
输出结果
员
员
Steven Spielberg 26
Woody Allen 22
Clint Eastwood 20
Martin Scorsese 20
序
序
..
Gavin Wiesen 1
Andrew Morahan 1
Luca Guadagnino 1
程
Richard Montoya 1
Name: director_name, Length: 2397, dtype: int64
程
actor_1_fb_likes.value_counts()
马
马
输出结果
1000.0 436
黑
11000.0 206
2000.0 189
3000.0 150
...
216.0 1
859.0 1
225.0 1
334.0 1
Name: actor_1_facebook_likes, Length: 877, dtype: int64
通过count()方法可以返回有多少非空值
director.count()
输出结果
4814
director.shape
输出结果
(4916,)
通过describe()方法打印描述信息
actor_1_fb_likes.describe()
输出结果
count 4909.000000
mean 6494.488491
std 15106.986884
min 0.000000
25% 607.000000
50% 982.000000
75% 11000.000000
max 640000.000000
Name: actor_1_facebook_likes, dtype: float64
director.describe()
输出结果
count 4814
unique 2397
top Steven Spielberg
freq 26
on
on
Name: director_name, dtype: object
Series的一些方法
方法 说明
th
th
append 连接两个或多个Series
corr 计算与另一个Series的相关系数
cov 计算与另一个Series的协方差
Py
describe 计算常见统计量
Py
drop_duplicates 返回去重之后的Series
equals 判断两个Series是否相同
员
员
get_values 获取Series的值,作用与values属性相同
hist 绘制直方图
序
序
isin Series中是否包含某些值
min 返回最小值
max 返回最大值
程
程
mean 返回算术平均值
median 返回中位数
马
马
mode 返回众数
quantile 返回指定位置的分位数
replace 用指定值代替Series中的值
黑
sample 返回Series的随机采样值
sort_values 对值进行排序
to_frame 把Series转换为DataFrame
unique 去重返回数组
2.3 Series的布尔索引
从Series中获取满足某些条件的数据,可以使用布尔索引
scientists = pd.read_csv('scientists.csv')
print(scientists)
输出结果
Name Born Died Age Occupation
0 Rosaline Franklin 1920-07-25 1958-04-16 37 Chemist
1 William Gosset 1876-06-13 1937-10-16 61 Statistician
2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse
3 Marie Curie 1867-11-07 1934-07-04 66 Chemist
4 Rachel Carson 1907-05-27 1964-04-14 56 Biologist
5 John Snow 1813-03-15 1858-06-16 45 Physician
6 Alan Turing 1912-06-23 1954-06-07 41 Computer Scientist
7 Johann Gauss 1777-04-30 1855-02-23 77 Mathematician
获取大于平均年龄的结果
ages = scientists['Age']
ages.mean()
输出结果
59.125
print(ages[ages>ages.mean()])
输出结果
on
on
1 61
2 90
3 66
7 77
Name: Age, dtype: int64
th
th
ages>ages.mean() 分析返回结果
print(ages>ages.mean())
Py
输出结果
Py
0 False
1 True
2 True
员
员
3 True
4 False
5 False
6 False
7 True
序
序
Name: Age, dtype: bool
从上面结果可以得知,从Series中获取部分数据,可以通过标签,索引,也可以传入布尔值的列表
程
#手动创建布尔值列表
程
bool_values = [False,True,True,False,False,False,False,False]
ages[bool_values]
输出结果
马
1 61
2 90
Name: Age, dtype: int64
黑
ages+100
输出结果
0 137
1 161
2 190
3 166
4 156
5 145
6 141
7 177
Name: Age, dtype: int64
ages*2
输出结果
0 74
1 122
2 180
3 132
4 112
5 90
6 82
7 154
Name: Age, dtype: int64
两个Series之间计算,如果Series元素个数相同,则将两个Series对应元素进行计算
ages+ages
输出结果
0 74
1 122
2 180
3 132
4 112
5 90
6 82
7 154
Name: Age, dtype: int64
on
on
ages*ages
输出结果
th
th
0 1369
1 3721
2 8100
3 4356
Py
4
5
3136
2025
Py
6 1681
7 5929
Name: Age, dtype: int64
员
员
元素个数不同的Series之间进行计算,会根据索引进行。索引不同的元素最终计算的结果会填充成缺失值,用NaN表示
ages + pd.Series([1,100])
序
序
输出结果
0 38.0
程
1
2
161.0
NaN
程
3 NaN
4 NaN
5 NaN
马
马
6 NaN
7 NaN
dtype: float64
黑
ages * pd.Series([1,100])
输出结果
0 37.0
1 6100.0
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
dtype: float64
Series之间进行计算时,数据会尽可能依据索引标签进行相互计算
print(ages)
输出结果
0 37
1 61
2 90
3 66
4 56
5 45
6 41
7 77
Name: Age, dtype: int64
rev_ages = ages.sort_index(ascending=False)
print(rev_ages)
输出结果
7 77
6 41
5 45
4 56
3 66
2 90
1 61
0 37
Name: Age, dtype: int64
on
on
使用ages 和 rev_ages 执行某项操作时,仍然是逐个元素进行,但在执行操作前会首先
print(ages*2)
输出结果
th
th
0 74
1 122
2 180
Py
3
4
132
112
Py
5 90
6 82
7 154
员
员
Name: Age, dtype: int64
print(ages+rev_ages)
序
序
输出结果
0 74
1 122
程
2
3
180
132
程
4 112
5 90
6 82
马
马
7 154
Name: Age, dtype: int64
3 DataFrame常用操作
黑
3.1 DataFrame的常用属性和方法
DataFrame是Pandas中最常见的对象,Series数据结构的许多属性和方法在DataFrame中也一样适用
movie = pd.read_csv('data/movie.csv')
# 打印行数和列数
movie.shape
输出结果
(4916, 28)
# 打印数据的个数
movie.size
输出结果
137648
# 该数据集的维度
movie.ndim
输出结果
# 该数据集的长度
len(movie)
输出结果
4916
# 各个列的值的个数
movie.count()
输出结果
color 4897
director_name 4814
num_critic_for_reviews 4867
duration 4901
...
actor_2_facebook_likes 4903
imdb_score 4916
on
on
aspect_ratio 4590
movie_facebook_likes 4916
Length: 28, dtype: int64
# 各列的最小值
th
th
movie.min()
输出结果
Py
num_critic_for_reviews
duration
1.00
7.00
Py
director_facebook_likes 0.00
actor_3_facebook_likes 0.00
...
员
员
actor_2_facebook_likes 0.00
imdb_score 1.60
aspect_ratio 1.18
movie_facebook_likes 0.00
Length: 16, dtype: float64
序
序
movie.describe()
输出结果
程
count 4867.000000 4901.000000 4814.000000 4893.000000 4909.000000 4.054000e+03 4.916000e+03 4916.000000 4903.000000 4895.000000 4.432000e+03 4810.000000 4903.000000 4916.0
mean 137.988905 107.090798 691.014541 631.276313 6494.488491 4.764451e+07 8.264492e+04 9579.815907 1.377320 267.668846 3.654749e+07 2002.447609 1621.923516 6.4
std 120.239379 25.286015 2832.954125 1625.874802 15106.986884 6.737255e+07 1.383222e+05 18164.316990 2.023826 372.934839 1.002427e+08 12.453977 4011.299523 1.1
min 1.000000 7.000000 0.000000 0.000000 0.000000 1.620000e+02 5.000000e+00 0.000000 0.000000 1.000000 2.180000e+02 1916.000000 0.000000 1.6
马
马
25% 49.000000 93.000000 7.000000 132.000000 607.000000 5.019656e+06 8.361750e+03 1394.750000 0.000000 64.000000 6.000000e+06 1999.000000 277.000000 5.8
50% 108.000000 103.000000 48.000000 366.000000 982.000000 2.504396e+07 3.313250e+04 3049.000000 1.000000 153.000000 1.985000e+07 2005.000000 593.000000 6.6
75% 191.000000 118.000000 189.750000 633.000000 11000.000000 6.110841e+07 9.377275e+04 13616.750000 2.000000 320.500000 4.300000e+07 2011.000000 912.000000 7.2
max 813.000000 511.000000 23000.000000 23000.000000 640000.000000 7.605058e+08 1.689764e+06 656730.000000 43.000000 5060.000000 4.200000e+09 2016.000000 137000.000000 9.5
3.2 DataFrame的布尔索引
黑
同Series一样,DataFrame也可以使用布尔索引获取数据子集。
# 使用布尔索引获取部分数据行
movie[movie['duration']>movie['duration'].mean()]
输出结果
color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_year a
Joel David Action|Adventure|Fantasy|Sci-
0 Color James Cameron 723.0 178.0 0.0 855.0 1000.0 760505847.0 ... 3054.0 English USA PG-13 237000000.0 2009.0
Moore Fi
Orlando
1 Color Gore Verbinski 302.0 169.0 563.0 1000.0 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0 2007.0
Bloom
2 Color Sam Mendes 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 Action|Adventure|Thriller ... 994.0 English UK PG-13 245000000.0 2015.0
Christopher
3 Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0
Nolan
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Brandon Alana
4893 NaN NaN 143.0 8.0 8.0 720.0 NaN Drama|Horror|Thriller ... 8.0 English USA NaN 17350.0 2011.0
Landers Kaniewski
4898 Color John Waters 73.0 108.0 0.0 105.0 Mink Stole 462.0 180483.0 Comedy|Crime|Horror ... 183.0 English USA NC-17 10000.0 1972.0
4899 Color Olivier Assayas 81.0 110.0 107.0 45.0 Béatrice Dalle 576.0 136007.0 Drama|Music|Romance ... 39.0 French France R 4500.0 2004.0
Kiyoshi Anna
4902 Color 78.0 111.0 62.0 6.0 89.0 94596.0 Crime|Horror|Mystery|Thriller ... 50.0 Japanese Japan NaN 1000000.0 1997.0
Kurosawa Nakagawa
可以传入布尔值的列表,来获取部分数据,True所对应的数据会被保留
movie.head()[[True,True,False,True,False]]
输出结果
color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_year actor
Action|Adventure|Fantasy|Sci-
0 Color James Cameron 723.0 178.0 0.0 855.0 Joel David Moore 1000.0 760505847.0 ... 3054.0 English USA PG-13 237000000.0
Fi
1 Color Gore Verbinski 302.0 169.0 563.0 1000.0 Orlando Bloom 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0
3 Color Christopher Nolan 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0
3 rows × 28 columns
3.3 DataFrame的运算
当DataFrame和数值进行运算时,DataFrame中的每一个元素会分别和数值进行运算
scientists*2
输出结果
1920-07-251920-07- 1958-04-161958-04-
0 Rosaline FranklinRosaline Franklin 74 ChemistChemist
25 16
1876-06-131876-06- 1937-10-161937-10-
1 William GossetWilliam Gosset 122 StatisticianStatistician
13 16
1867-11-071867-11- 1934-07-041934-07-
3 Marie CurieMarie Curie 132 ChemistChemist
07 04
on
on
1907-05-271907-05- 1964-04-141964-04-
4 Rachel CarsonRachel Carson 112 BiologistBiologist
27 14
1813-03-151813-03- 1858-06-161858-06-
5 John SnowJohn Snow 90 PhysicianPhysician
15 16
th
th
1912-06-231912-06- 1954-06-071954-06- Computer ScientistComputer
6 Alan TuringAlan Turing 82
23 07 Scientist
1777-04-301777-04- 1855-02-231855-02-
7 Johann GaussJohann Gauss 154 MathematicianMathematician
30 23
Py
在上面的例子中,数据都是字符串类型的,所有字符串都被复制了一份
Py
上面的例子,如果做其它计算会报错
两个DataFrame之间进行计算,会根据索引进行对应计算
员
员
scientists+scientists
输出结果
序
1920-07-251920-07- 1958-04-161958-04-
0 Rosaline FranklinRosaline Franklin 74 ChemistChemist
25 16
1876-06-131876-06- 1937-10-161937-10-
1 William GossetWilliam Gosset 122 StatisticianStatistician
13 16
程
2
Florence NightingaleFlorence 1820-05-121820-05- 1910-08-131910-08-
180
程
NurseNurse
Nightingale 12 13
1867-11-071867-11- 1934-07-041934-07-
3 Marie CurieMarie Curie 132 ChemistChemist
07 04
马
马
1907-05-271907-05- 1964-04-141964-04-
4 Rachel CarsonRachel Carson 112 BiologistBiologist
27 14
1813-03-151813-03- 1858-06-161858-06-
5 John SnowJohn Snow 90 PhysicianPhysician
15 16
1777-04-301777-04- 1855-02-231855-02-
7 Johann GaussJohann Gauss 154 MathematicianMathematician
30 23
两个DataFrame数据条目数不同时,会根据索引进行计算,索引不匹配的会返回NaN
first_half = scientists[:4]
scientists+first_half
输出结果
Name Born Died Age Occupation
1920-07-251920-07- 1958-04-161958-04-
0 Rosaline FranklinRosaline Franklin 74.0 ChemistChemist
25 16
1876-06-131876-06- 1937-10-161937-10-
1 William GossetWilliam Gosset 122.0 StatisticianStatistician
13 16
1867-11-071867-11- 1934-07-041934-07-
3 Marie CurieMarie Curie 132.0 ChemistChemist
07 04
4 更改Series和DataFrame
4.1 给行索引命名
加载数据文件时,如果不指定行索引,Pandas会自动加上从0开始的索引,可以通过set_index()方法重新设置行索引的名字
movie = pd.read_csv('data/movie.csv')
movie
on
on
输出结果
color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_year a
Orlando
1 Color Gore Verbinski 302.0 169.0 563.0 1000.0 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0 2007.0
th
th
Bloom
2 Color Sam Mendes 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 Action|Adventure|Thriller ... 994.0 English UK PG-13 245000000.0 2015.0
Christopher
3 Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0
Nolan
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
4912 Color NaN 43.0 43.0 NaN 319.0 Valorie Curry 841.0 NaN Crime|Drama|Mystery|Thriller ... 359.0 English USA TV-14 NaN NaN
Benjamin Maxwell
4913 Color 13.0 76.0 0.0 0.0 0.0 NaN Drama|Horror|Thriller ... 3.0 English USA NaN 1400.0 2013.0
Roberds Moody
Py
4914 Color
4915 Color
Daniel Hsia
Jon Gunn
14.0
43.0
100.0
90.0
0.0
16.0
Py
489.0
16.0
Daniel Henney
Brian
Herzlinger
946.0
86.0
10443.0
85222.0
Comedy|Drama|Romance
Documentary
...
...
9.0
84.0
English
English
USA
USA
PG-13
PG 1100.0
NaN 2012.0
2004.0
员
movie2 = movie.set_index('movie_title')
movie2
输出结果
序
序
movie_title color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_ye
Joel David Action|Adventure|Fantasy|Sci-
Avatar Color James Cameron 723.0 178.0 0.0 855.0 1000.0 760505847.0 ... 3054.0 English USA PG-13 237000000.0 2009.0
Moore Fi
Pirates of
the
Orlando
Caribbean: Color Gore Verbinski 302.0 169.0 563.0 1000.0 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0 2007.0
Bloom
At World's
End
程
Knight Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0
Nolan
Rises
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
The
Color NaN 43.0 43.0 NaN 319.0 Valorie Curry 841.0 NaN Crime|Drama|Mystery|Thriller ... 359.0 English USA TV-14 NaN NaN
Following
马
马
A Plague So Benjamin Maxwell
Color 13.0 76.0 0.0 0.0 0.0 NaN Drama|Horror|Thriller ... 3.0 English USA NaN 1400.0 2013.0
Pleasant Roberds Moody
Shanghai
Color Daniel Hsia 14.0 100.0 0.0 489.0 Daniel Henney 946.0 10443.0 Comedy|Drama|Romance ... 9.0 English USA PG-13 NaN 2012.0
Calling
My Date Brian
Color Jon Gunn 43.0 90.0 16.0 16.0 86.0 85222.0 Documentary ... 84.0 English USA PG 1100.0 2004.0
with Drew Herzlinger
加载数据的时候,可以通过通过index_col参数,指定使用某一列数据作为行索引
pd.read_csv('data/movie.csv', index_col='movie_title')
输出结果
movie_title color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_ye
Spectre Color Sam Mendes 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 Action|Adventure|Thriller ... 994.0 English UK PG-13 245000000.0 2015.0
The Dark
Christopher
Knight Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0
Nolan
Rises
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
The
Color NaN 43.0 43.0 NaN 319.0 Valorie Curry 841.0 NaN Crime|Drama|Mystery|Thriller ... 359.0 English USA TV-14 NaN NaN
Following
Shanghai
Color Daniel Hsia 14.0 100.0 0.0 489.0 Daniel Henney 946.0 10443.0 Comedy|Drama|Romance ... 9.0 English USA PG-13 NaN 2012.0
Calling
My Date Brian
Color Jon Gunn 43.0 90.0 16.0 16.0 86.0 85222.0 Documentary ... 84.0 English USA PG 1100.0 2004.0
with Drew Herzlinger
通过reset_index()方法可以重置索引
movie2.reset_index()
输出结果
movie_title color director_name num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross ... num_user_for_reviews language country content_rating budget title_year actor_2_faceboo
James Joel David
0 Avatar Color 723.0 178.0 0.0 855.0 1000.0 760505847.0 ... 3054.0 English USA PG-13 237000000.0 2009.0
Cameron Moore
Pirates of
the
Orlando
1 Caribbean: Color Gore Verbinski 302.0 169.0 563.0 1000.0 40000.0 309404152.0 ... 1238.0 English USA PG-13 300000000.0 2007.0
Bloom
At World's
End
2 Spectre Color Sam Mendes 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 ... 994.0 English UK PG-13 245000000.0 2015.0
The Dark Christopher
3 Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 ... 2701.0 English USA PG-13 250000000.0 2012.0 2
Knight Rises Nolan
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
The
4912 Color NaN 43.0 43.0 NaN 319.0 Valorie Curry 841.0 NaN ... 359.0 English USA TV-14 NaN NaN
Following
Shanghai
4914 Color Daniel Hsia 14.0 100.0 0.0 489.0 Daniel Henney 946.0 10443.0 ... 9.0 English USA PG-13 NaN 2012.0
Calling
My Date Brian
4915 Color Jon Gunn 43.0 90.0 16.0 16.0 86.0 85222.0 ... 84.0 English USA PG 1100.0 2004.0
with Drew Herzlinger
4.2 DataFrame修改行名和列名
DataFrame创建之后,可以通过rename()方法对原有的行索引名和列名进行修改
on
输出结果
th
movie.columns[:5]
Py
输出结果
Py
Index(['color', 'director_name', 'num_critic_for_reviews', 'duration',
'director_facebook_likes'],
dtype='object')
员
员
idx_rename = {'Avatar':'Ratava', 'Spectre': 'Ertceps'}
col_rename = {'director_name':'Director Name', 'num_critic_for_reviews': 'Critical Reviews'}
movie.rename(index=idx_rename, columns=col_rename).head()
序
序
输出结果
Director Critical
movie_title color duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_year actor_2_fac
Name Reviews
Ratava Color
Pirates of
the
Cameron
723.0 178.0 0.0 855.0
Moore
1000.0 760505847.0
Fi
...
程
3054.0 English USA PG-13 237000000.0 2009.0 936.0
Gore Orlando
Caribbean: Color 302.0 169.0 563.0 1000.0 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0 2007.0 5000.0
Verbinski Bloom
At World's
End
Sam
Ertceps Color 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 Action|Adventure|Thriller ... 994.0 English UK PG-13 245000000.0 2015.0 393.0
Mendes
马
马
The Dark Christopher
Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0 23000.0
Knight Rises Nolan
Star Wars:
Episode VII - Doug
NaN NaN NaN 131.0 NaN Rob Walker 131.0 NaN Documentary ... NaN NaN NaN NaN NaN NaN 12.0
The Force Walker
Awakens
5 rows × 27 columns
黑
如果不使用rename(),也可以将index 和 columns属性提取出来,修改之后,再赋值回去
index_list[0] = 'Ratava'
index_list[2] = 'Ertceps'
column_list[1] = 'Director Name'
column_list[2] = 'Critical Reviews'
movie.index = index_list
movie.columns = column_list
movie.head()
输出结果
Director Critical
color duration director_facebook_likes actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross genres ... num_user_for_reviews language country content_rating budget title_year actor_2_face
Name Reviews
Pirates of
the
Gore Orlando
Caribbean: Color 302.0 169.0 563.0 1000.0 40000.0 309404152.0 Action|Adventure|Fantasy ... 1238.0 English USA PG-13 300000000.0 2007.0
Verbinski Bloom
At World's
End
Sam
Ertceps Color 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 Action|Adventure|Thriller ... 994.0 English UK PG-13 245000000.0 2015.0
Mendes
The Dark
Christopher
Knight Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 Action|Thriller ... 2701.0 English USA PG-13 250000000.0 2012.0
Nolan
Rises
Star Wars:
Episode VII Doug
NaN NaN NaN 131.0 NaN Rob Walker 131.0 NaN Documentary ... NaN NaN NaN NaN NaN NaN
- The Force Walker
Awakens
5 rows × 27 columns
4.3 添加、删除、插入列
通过dataframe[列名]添加新列
movie = pd.read_csv('data/movie.csv')
movie['has_seen'] = 0
# 给新列赋值
movie['actor_director_facebook_likes'] = (movie['actor_1_facebook_likes'] +
movie['actor_2_facebook_likes'] +
movie['actor_3_facebook_likes'] +
movie['director_facebook_likes'])
调用drop方法删除列
on
on
movie = movie.drop('actor_director_facebook_likes', axis='columns')
th
movie.insert(loc=0,column='profit',value=movie['gross'] - movie['budget'])
movie
输出结果
Py
Orlando
1 9404152.0 Color Gore Verbinski 302.0 169.0 563.0 1000.0 40000.0 309404152.0 ... English USA PG-13 300000000.0 2007.0 5000.0 7.1
Bloom
2 -44925825.0 Color Sam Mendes 602.0 148.0 0.0 161.0 Rory Kinnear 11000.0 200074175.0 ... English UK PG-13 245000000.0 2015.0 393.0 6.8
员
员
Christopher
3 198130642.0 Color 813.0 164.0 22000.0 23000.0 Christian Bale 27000.0 448130642.0 ... English USA PG-13 250000000.0 2012.0 23000.0 8.5
Nolan
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
4912 NaN Color NaN 43.0 43.0 NaN 319.0 Valorie Curry 841.0 NaN ... English USA TV-14 NaN NaN 593.0 7.5
Benjamin Maxwell
4913 NaN Color 13.0 76.0 0.0 0.0 0.0 NaN ... English USA NaN 1400.0 2013.0 0.0 6.3
Roberds Moody
4914 NaN Color Daniel Hsia 14.0 100.0 0.0 489.0 Daniel Henney 946.0 10443.0 ... English USA PG-13 NaN 2012.0 719.0 6.3
Brian
4915 84122.0 Color Jon Gunn 43.0 90.0 16.0 16.0 86.0 85222.0 ... English USA PG 1100.0 2004.0 23.0 6.6
序
序
Herzlinger
5 导出和导入数据
程
程
5.1 pickle文件
保存成pickle文件
马
马
调用to_pickle方法将以二进制格式保存数据
如要保存的对象是计算的中间结果,或者保存的对象以后会在Python中复用,可把对象保存为.pickle文件
如果保存成pickle文件,只能在python中使用
文件的扩展名可以是.p,.pkl,.pickle
黑
scientists = pd.read_csv('data/scientists.csv')
names = scientists['Name']
names.to_pickle('output/scientists_name.pickle')
scientists.to_pickle('output/scientists_df.pickle')
读取pickle文件
可以使用pd.read_pickle函数读取.pickle文件中的数据
scientists_name = pd.read_pickle('output/scientists_name.pickle')
print(scientists_name)
输出结果
0 Rosaline Franklin
1 William Gosset
2 Florence Nightingale
3 Marie Curie
4 Rachel Carson
5 John Snow
6 Alan Turing
7 Johann Gauss
Name: Name, dtype: object
5.2 CSV文件
保存成CSV文件
CSV(逗号分隔值)是很灵活的一种数据存储格式
在CSV文件中,对于每一行,各列采用逗号分隔
除了逗号,还可以使用其他类型的分隔符,比如TSV文件,使用制表符作为分隔符
CSV是数据协作和共享的首选格式
names.to_csv('output/scientists_name.csv')
#设置分隔符为\t
scientists.to_csv('output/scientists_df.tsv',sep='\t')
不在csv文件中写行名
scientists.to_csv('output/scientists_df_noindex.csv',index=False)
5.3 Excel文件
保存成Excel文件
Series这种数据结构不支持to_excel方法,想保存成Excel文件,需要把Series转换成DataFrame
names_df = names.to_frame()
import xlwt
on
on
names_df.to_excel('output/scientists_name_df.xls')
names_df.to_excel('output/scientists_name_df.xlsx')
把DataFrame保存为Excel格式
th
th
scientists.to_excel('output/scientists_df.xlsx',sheet_name='scientists',index=False)
读取Excel文件
使用pd.read_excel() 读取Excel文件
Py
Py
pd.read_excel('output/scientists_df.xlsx')
输出结果
员
员
Name Born Died Age Occupation
序
1 William Gosset 1876-06-13 1937-10-16 61 Statistician
注意 pandas读写excel需要额外安装如下三个包
黑
5.4 其它数据格式
feather文件
feather是一种文件格式,用于存储二进制对象
feather对象也可以加载到R语言中使用
feather格式的主要优点是在Python和R语言之间的读写速度要比CSV文件快
feather数据格式通常只用中间数据格式,用于Python和R之间传递数据
一般不用做保存最终数据
导出方法 说明
to_clipboard 把数据保存到系统剪贴板,方便粘贴
to_dict 把数据转换成Python字典
to_hdf 把数据保存为HDF格式
to_html 把数据转换成HTML
to_json 把数据转换成JSON字符串
to_sql 把数据保存到SQL数据库
小结
创建Series和DataFrame
pd.Series
pd.DataFrame
Series常用操作
常用属性
index
on
on
values
shape,size,dtype
常用方法
max(),min(),std()
count()
th
th
describe()
布尔索引
运算
与数值之间进行算数运算会对每一个元素进行计算
Py
DataFrame常用操作
两个Series之间进行计算会索引对齐
Py
常用属性
常用方法
布尔索引
员
员
运算
更改Series和DataFrame
指定行索引名字
序
序
dataframe.set_index()
dataframe.reset_index()
修改行/列名字
dataframe.rename(index=,columns = )
程
获取行/列索引 转换成list之后,修改list再赋值回去
程
添加、删除、插入列
添加 dataframe['新列‘']
删除 dataframe.drop
马
马
插入列 dataframe.insert()
导入导出数据
pickle
csv
黑
Excel
feather
05_Pandas 数据分析入门
学习目标
掌握在Pandas中计算常用统计量的方法
熟练使用pandas进行简单排序、分组、聚合等计算
1 计算常用统计值
加载数据之后,可以通过计算最大值,最小值,平均值,分位数,方差等方式对数据的分布情况做基本了解
college = pd.read_csv('data/college.csv')
college.head()
输出结果
INSTNM CITY STABBR HBCU MENONLY WOMENONLY RELAFFIL SATVRMID SATMTMID DISTANCEONLY ... UGDS_2MOR UGDS_NRA UGDS_UNKN PPTUG_EF CURROPER PCTPELL PCTFLOAN UG25ABV MD_EARN_WNE_P10 GRAD_DEBT_MDN_SUPP
Alabama A
0 &M Normal AL 1.0 0.0 0.0 0 424.0 420.0 0.0 ... 0.0000 0.0059 0.0138 0.0656 1 0.7356 0.8284 0.1049 30300 33888
University
University
of Alabama
1 Birmingham AL 0.0 0.0 0.0 0 570.0 565.0 0.0 ... 0.0368 0.0179 0.0100 0.2607 1 0.3460 0.5214 0.2422 39700 21941.5
at
Birmingham
Amridge
2 Montgomery AL 0.0 0.0 0.0 1 NaN NaN 1.0 ... 0.0000 0.0000 0.2715 0.4536 1 0.6801 0.7795 0.8540 40100 23370
University
University
3 of Alabama Huntsville AL 0.0 0.0 0.0 0 595.0 590.0 0.0 ... 0.0172 0.0332 0.0350 0.2146 1 0.3072 0.4596 0.2640 45500 24097
in Huntsville
Alabama
4 State Montgomery AL 1.0 0.0 0.0 0 425.0 430.0 0.0 ... 0.0098 0.0243 0.0137 0.0892 1 0.7347 0.7554 0.1270 26600 33118.5
University
5 rows × 27 columns
数据字段说明
college.columns
输出结果
on
列名 字段含义
INSTNM 大学名称
CITY 所在城市
th
th
STABBR 所在州简称
HBCU 历史上的黑人学员和大学
WOMENONLY
Py
0/1 只有女学生
员
SATMTMID SAT考试 数学分数中位数
DISTANCEONLY 只接受远程教育学生
序
序
UGDS 本科招生
UGDS_WHITE 本科生白人比例
UGDS_BLACK 本科生黑人比例
程
UGDS_HISP 本科生拉丁裔比例
程
UGDS_ASIAN 本科生亚裔比例
UGDS_AIAN 本科生美洲印第安人/阿拉斯加土著比例
马
UGDS_NHPI 本科生夏威夷/太平洋群岛土著比例
UGDS_2MOR 本科生混血比例
黑
UGDS_NRA 本科生中留学生比例
UGDS_UNKN 本科生未知族裔比例
PPTUG_EF 非全日制学生比例
PCTPELL 佩尔资助计划学生比例
PCTFLOAN 学费贷款学生比例
UG25ABV 年龄大于25岁的学生比例
MD_EARN_WNE_P10 入学10年后收入中位数
GRAD_DEBT_MDN_SUPP 毕业生债务中位数
- 查看数据行列数
college.shape
输出结果
(7535, 27)
统计数值列,并进行转置
college.describe().T
输出结果
on
PPTUG_EF 6853.0 0.226639 0.246470 0.0 0.000000 0.15040 0.376900 1.0000
统计对象和类型列
th
th
pandas 基于 numpy,numpy支持的数据类型,pandas都支持
np.object 字符串类型
pd.Categorical 类别类型
Py
import numpy as np
Py
college.describe(include=[np.object, pd.Categorical]).T
输出结果
员
员
count unique top freq
序
CITY 7535 2514 New York 87
通过info 方法了解不同字段的条目数量,数据类型,是否缺失及内存占用情况
马
马
college.info()
输出结果
黑
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7535 entries, 0 to 7534
Data columns (total 27 columns):
INSTNM 7535 non-null object
CITY 7535 non-null object
STABBR 7535 non-null object
HBCU 7164 non-null float64
MENONLY 7164 non-null float64
WOMENONLY 7164 non-null float64
RELAFFIL 7535 non-null int64
SATVRMID 1185 non-null float64
SATMTMID 1196 non-null float64
DISTANCEONLY 7164 non-null float64
UGDS 6874 non-null float64
UGDS_WHITE 6874 non-null float64
UGDS_BLACK 6874 non-null float64
UGDS_HISP 6874 non-null float64
UGDS_ASIAN 6874 non-null float64
UGDS_AIAN 6874 non-null float64
UGDS_NHPI 6874 non-null float64
UGDS_2MOR 6874 non-null float64
UGDS_NRA 6874 non-null float64
UGDS_UNKN 6874 non-null float64
PPTUG_EF 6853 non-null float64
CURROPER 7535 non-null int64
PCTPELL 6849 non-null float64
PCTFLOAN 6849 non-null float64
UG25ABV 6718 non-null float64
MD_EARN_WNE_P10 6413 non-null object
GRAD_DEBT_MDN_SUPP 7503 non-null object
dtypes: float64(20), int64(2), object(5)
memory usage: 1.6+ MB
2 常用排序方法
2.1 从最大的N个值中选取最小值——找到小成本高口碑电影
movie = pd.read_csv('data/movie.csv')
movie2 = movie[['movie_title', 'imdb_score', 'budget']]
movie2.head()
输出结果
on
3 The Dark Knight Rises 8.5 250000000.0
用nlargest方法,选出imdb_score分数最高的100个
th
th
movie2.nlargest(100, 'imdb_score').head()
输出结果
Py
movie_title
Py
imdb_score budget
员
3402 The Godfather 9.2 6000000.0
序
4312 Kickboxer: Vengeance 9.1 17000000.0
使用nsmallest方法再从中挑出预算最小的五部
程
2.2 通过排序选取每组的最大值——找到每年imdb评分最高的电影
sort_values 按照年排序,ascending 升序排列
输出结果
movie_title title_year imdb_score
同时对'title_year','imdb_score' 两列进行排序
输出结果
on
27 Captain America: Civil War 2016.0 8.2
用drop_duplicates去重,只保留每年的第一条数据
th
th
movie_top_year = movie3.drop_duplicates(subset='title_year')
movie_top_year.head()
Py
输出结果
Py
movie_title title_year imdb_score
员
3745 Running Forever 2015.0 8.6
序
3 The Dark Knight Rises 2012.0 8.5
2.3 提取出每年,每种电影分级中预算少的电影——sort_values多列排序
程
程
多列排序时,ascending 参数传入一个列表,排序一一对应
马
# 多列排序,ascending=[False, False, True] 降序,降序,升序
movie4_sorted = movie4.sort_values(['title_year', 'content_rating', 'budget'], ascending=[False, False,
True])
# 去重,删除'title_year', 'content_rating'相同的数据
movie4_sorted.drop_duplicates(subset=['title_year', 'content_rating']).head(10)
黑
输出结果
3 简单数据分析练习(租房数据)
载入数据
import pandas as pd
house_data = pd.read_csv('data/LJdata.csv')
把列名替换成英文
#查看原始列名
house_data.columns
输出结果
Index(['区域', '地址', '标题', '户型', '面积', '价格', '楼层', '建造时间', '朝向', '更新时间', '看房人数','备
注', '链接地址'],dtype='object')
查看数据基本情况
house_data.head()
输出结果
on
on
district address title house_type area price floor build_time direction update_time view_num extra_info link
广安门 远见名 远见名苑 东向两居室 独立小区环境 86平 低楼层 2006年建塔 距离7号线达官营站684米 随时 https://bj.lianjia.com/zufa
2 2室1厅 8000 东 2017.07.20 34
租房 苑 适合居家 米 (共25层) 楼 看房 精装修 自供暖 ng/101101756753.html
th
th
天通苑 天通苑 北一区简装两居,采光好,视野美, 103 低楼层 2004年建板 距离5号线天通苑站927米 随时 https://bj.lianjia.com/zufa
3 2室1厅 5300 东南 2017.07.25 30
租房 北一区 出行方便 平米 (共13层) 楼 看房 精装修 集中供暖 ng/101101780034.html
house_data.info()
Py
Py
输出结果
<class 'pandas.core.frame.DataFrame'>
员
员
RangeIndex: 2760 entries, 0 to 2759
Data columns (total 13 columns):
district 2760 non-null object
address 2760 non-null object
title 2760 non-null object
序
序
house_type 2760 non-null object
area 2760 non-null object
price 2760 non-null int64
floor 2760 non-null object
build_time 2758 non-null object
程
马
dtypes: int64(2), object(11)
memory usage: 280.4+ KB
house_data.shape
黑
输出结果
(2760, 13)
house_data.describe()
输出结果
price view_num
找到租金最低,和租金最高的房子
house_data.loc[house_data['price']==210000]
house_data.loc[house_data['price']==1300]
house_data[house_data['price']==house_data['price'].min()]
house_data[house_data['price']==house_data['price'].max()]
on
house_data.sort_values(by='price').head(1)
输出结果
district address title house_type area price floor build_time direction update_time view_num extra_info link
th
th
2527 良乡租房 伟业嘉园西里 半地下室 家电齐全 集中供暖 简单装修 1室1厅 46平米 1300 地下室(共5层) 2005年建 南 2017.07.19 14 随时看房 集中供暖
house_data.sort_values(by='price').tail(1)
Py
输出结果
Py
district address title house_type area price floor build_time direction update_time view_num extra_info link
和平里租 雍和家园二 雍和家园 底商出租 使用面积720 720平 低楼层(共6 2005年建板 距离2号线雍和宫站293米 随时看房 集中
2658 6室3厅 210000 南 2017.07.26 21
房 期 米 米 层) 楼 供暖
找到最近新上的10套房源
员
员
house_data.sort_values(by='update_time', ascending=False).head()
输出结果
序
序
district address title house_type area price floor build_time direction update_time view_num extra_info link
2405
武夷花
园租房
月亮城
堡
月亮城堡精装修正规一居 南北通透 户
型好
1室1厅
102
平米
4500
低楼层
(共11层)
2005年建
板塔结合
南北 2017.07.27 28
程
随时看房 精装修 集中供暖
https://bj.lianjia.com/zufa
ng/101101495373.html
马甸租 北太平 牡丹园精装一居室 可当两居 350米到 33平 低楼层 1985年建 距离10号线牡丹园站335米 https://bj.lianjia.com/zufa
2366 1室1厅 4000 南 2017.07.27 24
房 庄路 10号线 南北通透 米 (共5层) 板楼 随时看房 精装修 集中供暖 ng/101101622752.html
查看所有更新时间
马
#查看所有更新时间
house_data['update_time'].unique()
黑
输出结果
看房人数
house_data['view_num'].mean() #平均值
house_data['view_num'].median() #中位数
tmp_df = house_data.groupby('view_num',as_index=False)['district'].count()
tmp_df.columns = ['view_num', 'count']
tmp_df.head()
输出结果
view_num count
0 0 152
1 1 149
2 2 143
3 3 129
4 4 147
%matplotlib inline
tmp_df['count'].plot(kind='bar',figsize=(20,10))
输出结果
<matplotlib.axes._subplots.AxesSubplot at 0x14e1763fc88>
on
on
th
th
Py
Py
员
员
房子价格的分布
序
序
print(house_data['price'].mean()) #平均值
print(house_data['price'].std()) #方差
print(house_data['price'].median()) #中位数
程
输出结果
程
7570.800724637681
6316.204986067457
6000.0
马
马
看房人数最多的朝向
popular_direction = house_data.groupby('direction',as_index=False)[['view_num']].sum()
黑
popular_direction[popular_direction['view_num']==popular_direction['view_num'].max()]
输出结果
direction view_num
23 南北 11785
房型分布情况
house_type_dis = house_data.groupby(['house_type']).count()
%matplotlib inline
house_type_dis['district'].plot(kind='bar') #柱状图
输出结果
<matplotlib.axes._subplots.AxesSubplot at 0x14e17db17b8>
最受欢迎的房型
tmp = house_data.groupby('house_type',as_index=False).agg({'view_num':'sum'})
on
on
tmp[tmp['view_num']==tmp['view_num'].max()]
输出结果
house_type view_num
th
th
5 2室1厅 17589
房子的平均租房价格 (元/平米)
Py
Py
house_data.loc[:,'price_per_m2'] = house_data['price']/house_data['area']
house_data['price_per_m2'].mean()
输出结果
员
员
87.72268429900454
热门小区
序
序
address_df = house_data[['address','view_num']].groupby(['address'],as_index=False).sum()
address_df.sort_values(by='view_num', ascending=False).head()
输出结果
程
程
address view_num
马
369 卡布其诺 245
出租房源最多的小区
tmp_df2 = house_data[['address','view_num']].groupby(['address'],as_index=False).count()
tmp_df2.columns = ['address','count']
tmp_df2.nlargest(columns='count', n=1)
输出结果
address count
1288 远洋山水 19
小结
掌握在Pandas中计算常用统计量的方法
info 了解不同字段的条目数量,数据类型,是否缺失及内存占用情况
describe 计算数值类型数据的常用统计量,
熟练使用pandas进行简单排序、分组、聚合等计算
nlargest 返回指定字段的前n个最大值
nsmallest 返回指定字段的前n个最小值
sort_values 指定字段,按值排序
groupby 按字段分组
agg 分组之后聚合
06_数据组合
学习目标
熟练使用Pandas连接数据
熟练使用Pandas合并数据集
1 简介
在动手进行数据分析工作之前,需要进行数据清理工作,数据清理的主要目标是
每个观测值成一行
每个变量成一列
每种观测单元构成一张表格
数据整理好之后,可能需要多张表格组合到一起才能进行某些问题的分析
一张表保存公司名称,另一张表保存股票价格
单个数据集也可能会分割成多个,比如时间序列数据,每个日期可能再一个单独的文件中
on
on
2 连接数据
组合数据的一种方法是使用“连接”(concatenation)
连接是指把某行或某列追加到数据中
th
th
数据被分成了多份可以使用连接把数据拼接起来
把计算的结果追加到现有数据集,可以使用连接
2.1 添加行
Py
加载多份数据,并连接起来
Py
import pandas as pd
df1 = pd.read_csv('data/concat_1.csv')
员
员
df2 = pd.read_csv('data/concat_2.csv')
df3 = pd.read_csv('data/concat_3.csv')
print(df1)
显示结果:
序
序
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
程
2 a2 b2 c2 d2
程
3 a3 b3 c3 d3
print(df2)
马
马
显示结果:
A B C D
黑
0 a4 b4 c4 d4
1 a5 b5 c5 d5
2 a6 b6 c6 d6
3 a7 b7 c7 d7
print(df3)
显示结果:
A B C D
0 a8 b8 c8 d8
1 a9 b9 c9 d9
2 a10 b10 c10 d10
3 a11 b11 c11 d11
可以使用concat函数将上面3个DataFrame连接起来,需将3个DataFrame放到同一个列表中
row_concat = pd.concat([df1,df2,df3])
print(row_concat)
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
0 a4 b4 c4 d4
1 a5 b5 c5 d5
2 a6 b6 c6 d6
3 a7 b7 c7 d7
0 a8 b8 c8 d8
1 a9 b9 c9 d9
2 a10 b10 c10 d10
3 a11 b11 c11 d11
row_concat.iloc[3,]
显示结果:
A a3
B b3
C c3
on
on
D d3
Name: 3, dtype: object
row_concat.loc[3,]
th
th
显示结果:
A B C D
3 a3 b3 c3 d3
3 a7 b7 c7 d7
Py
new_series = pd.Series(['n1','n2','n3','n4'])
员
员
print(new_series)
显示结果:
序
序
0 n1
1 n2
2 n3
3 n4
程
dtype: object
程
pd.concat([df1,new_series])
马
马
显示结果:
A B C D 0
0 a0 b0 c0 d0 NaN
黑
1 a1 b1 c1 d1 NaN
2 a2 b2 c2 d2 NaN
3 a3 b3 c3 d3 NaN
上面的结果中包含NaN值,NaN是Python用于表示“缺失值”的方法,由于Series是列数据,concat方法默认是添加行,由于
Series数据没有行索引,所以添加了一个新列,缺失的数据用NaN填充
如果想将['n1','n2','n3','n4']作为行连接到df1后,可以创建DataFrame并指定列名
# 注意[['n1','n2','n3','n4']] 是两个中括号
new_row_df = pd.DataFrame([['n1','n2','n3','n4']],columns=['A','B','C','D'])
print(new_row_df)
显示结果:
A B C D
0 n1 n2 n3 n4
print(pd.concat([df1,new_row_df]))
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
0 n1 n2 n3 n4
concat可以连接多个对象,如果只需要向现有DataFrame追加一个对象,可以通过append函数来实现
print(df1.append(df2))
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
on
on
2 a2 b2 c2 d2
3 a3 b3 c3 d3
0 a4 b4 c4 d4
1 a5 b5 c5 d5
2 a6 b6 c6 d6
3 a7 b7 c7 d7
th
th
print(df1.append(new_row_df))
显示结果:
Py
Py
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
员
员
3 a3 b3 c3 d3
0 n1 n2 n3 n4
使用Python字典添加数据行
序
序
data_dict = {'A':'n1','B':'n2','C':'n3','D':'n4'}
df1.append(data_dict,ignore_index=True)
显示结果:
程
程
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
马
马
2 a2 b2 c2 d2
3 a3 b3 c3 d3
4 n1 n2 n3 n4
上面的例子中,向DataFrame中append一个字典的时候,必须传入ignore_index = True
黑
如果是两个或者多个DataFrame连接,可以通过ignore_index = True参数,忽略后面DataFrame的索引
row_concat_ignore_index = pd.concat([df1,df2,df3],ignore_index=True)
print(row_concat_ignore_index)
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
4 a4 b4 c4 d4
5 a5 b5 c5 d5
6 a6 b6 c6 d6
7 a7 b7 c7 d7
8 a8 b8 c8 d8
9 a9 b9 c9 d9
10 a10 b10 c10 d10
11 a11 b11 c11 d11
2.2 添加列
使用concat函数添加列,与添加行的方法类似,需要多传一个axis参数 axis的默认值是index 按行添加,传入参数 axis = columns 即
可按列添加
col_concat = pd.concat([df1,df2,df3],axis=1)
print(col_concat)
显示结果:
A B C D A B C D A B C D
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d11
通过列名获取子集
print(col_concat['A'])
显示结果:
A A A
0 a0 a4 a8
1 a1 a5 a9
2 a2 a6 a10
on
on
3 a3 a7 a11
向DataFrame添加一列,不需要调用函数,通过dataframe['列名'] = ['值'] 即可
col_concat['new_col'] = ['n1','n2','n3','n4']
th
th
print(col_concat)
显示结果:
Py
A
0
B
a0 b0
C D
c0
A
d0
B
a4
C
b4
D
c4 d4
A
a8
B
b8
C
Py
c8
D new_col
d8 n1
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9 n2
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10 n3
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d11 n4
员
员
也可以通过dataframe['列名'] = Series对象 这种方式添加一列
col_concat['new_col_series'] = pd.Series(['n1','n2','n3','n4'])
col_concat
序
序
显示结果:
A B C D A B C D A B C D new_col new_col_series
程
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8 n1 n1
程
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9 n2 n2
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10 n3 n3
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d11 n4 n4
马
马
按列合并数据之后,可以重置列索引,获得有序索引
print(pd.concat([df1,df2,df3],axis = 'columns',ignore_index=True))
黑
显示结果:
0 1 2 3 4 5 6 7 8 9 10 11
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d11
2.3 concat连接具有不同行列索引的数据
将上面例子中的数据集做调整,修改列名
df1.columns = ['A','B','C','D']
df2.columns = ['E','F','G','H']
df3.columns = ['A','C','F','H']
print(df1)
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
print(df2)
显示结果:
E F G H
0 a4 b4 c4 d4
1 a5 b5 c5 d5
2 a6 b6 c6 d6
3 a7 b7 c7 d7
print(df3)
显示结果:
A C F H
0 a8 b8 c8 d8
1 a9 b9 c9 d9
on
on
2 a10 b10 c10 d10
3 a11 b11 c11 d11
使用concat直接连接,数据会堆叠在一起,列名相同的数据会合并到一列,合并后不存在的数据会用NaN填充
th
th
row_concat = pd.concat([df1,df2,df3])
print(row_concat)
显示结果:
Py
A B C D E F G H
Py
0 a0 b0 c0 d0 NaN NaN NaN NaN
1 a1 b1 c1 d1 NaN NaN NaN NaN
2 a2 b2 c2 d2 NaN NaN NaN NaN
3 a3 b3 c3 d3 NaN NaN NaN NaN
员
员
0 NaN NaN NaN NaN a4 b4 c4 d4
1 NaN NaN NaN NaN a5 b5 c5 d5
2 NaN NaN NaN NaN a6 b6 c6 d6
3 NaN NaN NaN NaN a7 b7 c7 d7
序
序
0 a8 NaN b8 NaN NaN c8 NaN d8
1 a9 NaN b9 NaN NaN c9 NaN d9
2 a10 NaN b10 NaN NaN c10 NaN d10
3 a11 NaN b11 NaN NaN c11 NaN d11
程
程
如果在连接的时候只想保留所有数据集中都有的数据,可以使用join参数,默认是'outer'保留所有数据,如果设置为'inner' 只保
留数据中的共有部分
print(pd.concat([df1,df2,df3],join='inner'))
马
马
显示结果:
Empty DataFrame
黑
Columns: []
Index: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
print(pd.concat([df1,df3],join='inner'))
显示结果:
A C
0 a0 c0
1 a1 c1
2 a2 c2
3 a3 c3
0 a8 b8
1 a9 b9
2 a10 b10
3 a11 b11
连接具有不同行索引的数据
df1.index = [0,1,2,3]
df2.index = [4,5,6,7]
df3.index = [0,2,5,7]
print(df1)
显示结果:
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
print(df2)
显示结果:
E F G H
4 a4 b4 c4 d4
5 a5 b5 c5 d5
6 a6 b6 c6 d6
7 a7 b7 c7 d7
print(df3)
显示结果:
on
on
A C F H
0 a8 b8 c8 d8
2 a9 b9 c9 d9
5 a10 b10 c10 d10
th
th
7 a11 b11 c11 d11
col_concat = pd.concat([df1,df2,df3],axis='columns')
print(col_concat)
Py
显示结果:
员
员
A B C D E F G H A C F H
0 a0 b0 c0 d0 NaN NaN NaN NaN a8 b8 c8 d8
1 a1 b1 c1 d1 NaN NaN NaN NaN NaN NaN NaN NaN
2 a2 b2 c2 d2 NaN NaN NaN NaN a9 b9 c9 d9
3 a3 b3 c3 d3 NaN NaN NaN NaN NaN NaN NaN NaN
序
序
4 NaN NaN NaN NaN a4 b4 c4 d4 NaN NaN NaN NaN
5 NaN NaN NaN NaN a5 b5 c5 d5 a10 b10 c10 d10
6 NaN NaN NaN NaN a6 b6 c6 d6 NaN NaN NaN NaN
7 NaN NaN NaN NaN a7 b7 c7 d7 a11 b11 c11 d11
程
使用join = 'inner'参数,只保留索引匹配的结果
程
print(pd.concat([df1,df3],axis = 1,join = 'inner'))
马
马
显示结果:
A B C D A C F H
0 a0 b0 c0 d0 a8 b8 c8 d8
黑
2 a2 b2 c2 d2 a9 b9 c9 d9
3 合并多个数据集
在使用concat连接数据时,涉及到了参数join(join = 'inner',join = 'outer')
数据库中可以依据共有数据把两个或者多个数据表组合起来,即join操作
DataFrame 也可以实现类似数据库的join操作
Pandas可以通过pd.join命令组合数据,也可以通过pd.merge命令组合数据
merge更灵活
如果想依据行索引来合并DataFrame可以考虑使用join函数
加载数据:
显示结果:
TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice
For Those About To Rock (We Salute Angus Young, Malcolm Young, Brian
0 1 1 1 1 343719 11170334 0.99
You) Johnson
4 5 Princess of the Dawn 3 2 1 Deaffy & R.A. Smith-Diesel 375418 6290521 0.99
read_sql_table函数可以从数据库中读取表,第一个参数是表名,第二个参数是数据库连接对象
显示结果:
GenreId Name
0 1 Rock
1 2 Jazz
2 3 Metal
3 4 Alternative & Punk
4 5 Rock And Roll
5 6 Blues
6 7 Latin
7 8 Reggae
on
on
8 9 Pop
9 10 Soundtrack
10 11 Bossa Nova
11 12 Easy Listening
12 13 Heavy Metal
13 14 R&B/Soul
th
th
14 15 Electronica/Dance
15 16 World
16 17 Hip Hop/Rap
17 18 Science Fiction
18 19 TV Shows
Py
19
20
20
21
Sci Fi & Fantasy
Drama
Py
21 22 Comedy
22 23 Alternative
23 24 Classical
员
员
24 25 Opera
3.1 一对一合并
序
序
最简单的合并只涉及两个DataFrame——把一列与另一列连接,且要连接的列不含任何重复值
先从tracks中提取部分数据,使其不含重复的'GenreId'值
tracks_subset = tracks.loc[[0,62,76,98,110,193,204,281,322,359],]
程
tracks_subset
程
显示结果:
马
0 1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.99
98 99 Your Time Has Come 11 1 4 Cornell, Commerford, Morello, Wilk 255529 8273592 0.99
黑
110 111 Money 12 1 5 Berry Gordy, Jr./Janie Bradford 147591 2365897 0.99
193 194 First Time I Met The Blues 20 1 6 Eurreal Montgomery 140434 4604995 0.99
281 282 Girassol 26 1 8 Bino Farias/Da Gama/Lazão/Pedro Luis/Toni Garrido 249808 8327676 0.99
322 323 Dig-Dig, Lambe-Lambe (Ao Vivo) 29 1 9 Cassiano Costa/Cintia Maviane/J.F./Lucas Costa 205479 6892516 0.99
通过'GenreId'列合并数据,how参数指定连接方式
how = ’left‘ 对应SQL中的 left outer 保留左侧表中的所有key
how = ’right‘ 对应SQL中的 right outer 保留右侧表中的所有key
how = 'outer' 对应SQL中的 full outer 保留左右两侧侧表中的所有key
how = 'inner' 对应SQL中的 inner 只保留左右两侧都有的key
显示结果:
on
显示结果:
th
3 4 Alternative & Punk 99 255529
4 5 Rock And Roll 111 147591
5 6 Blues 194 140434
6 7 Latin 205 177397
Py
7
8
8
9
Reggae
Pop
282
323
Py 249808
205479
9 10 Soundtrack 360 276349
3.2 多对一合并
员
员
与上一小节的代码一样,这次不使用tracks的子集,而是直接使用tracks的全部数据
序
print(genre_track)
显示结果:
程
马
3 1 Rock 4 252051
4 1 Rock 5 375418
5 1 Rock 6 205662
6 1 Rock 7 233926
7 1 Rock 8 210834
黑
8 1 Rock 9 203102
9 1 Rock 10 263497
10 1 Rock 11 199836
11 1 Rock 12 263288
12 1 Rock 13 205688
13 1 Rock 14 270863
14 1 Rock 15 331180
15 1 Rock 16 215196
16 1 Rock 17 366654
17 1 Rock 18 267728
18 1 Rock 19 325041
19 1 Rock 20 369319
20 1 Rock 21 254380
21 1 Rock 22 323761
22 1 Rock 23 295680
23 1 Rock 24 321828
24 1 Rock 25 264698
25 1 Rock 26 310622
26 1 Rock 27 264855
27 1 Rock 28 330736
28 1 Rock 29 309263
29 1 Rock 30 356519
... ... ... ... ...
3473 24 Classical 3449 120000
3474 24 Classical 3450 253422
3475 24 Classical 3452 101293
3476 24 Classical 3453 253281
3477 24 Classical 3454 362933
3478 24 Classical 3479 339567
3479 24 Classical 3480 299350
3480 24 Classical 3481 387826
3481 24 Classical 3482 225933
3482 24 Classical 3483 110266
3483 24 Classical 3484 289388
3484 24 Classical 3485 567494
3485 24 Classical 3486 364296
3486 24 Classical 3487 385506
3487 24 Classical 3488 142081
3488 24 Classical 3489 376510
3489 24 Classical 3490 285673
3490 24 Classical 3491 234746
3491 24 Classical 3492 133768
3492 24 Classical 3493 333669
3493 24 Classical 3494 286998
3494 24 Classical 3495 265541
3495 24 Classical 3496 51780
3496 24 Classical 3497 261849
3497 24 Classical 3498 493573
3498 24 Classical 3499 286741
3499 24 Classical 3500 139200
3500 24 Classical 3501 66639
3501 24 Classical 3502 221331
on
on
3502 25 Opera 3451 174813
如上面结果所示,Name的值在合并后的数据中被复制了
计算每种类型音乐的平均时长
to_timedelta 将Milliseconds列转变为timedelta数据类型
th
th
参数unit='ms' 时间单位
dt.floor('s') dt.floor() 时间类型数据,按指定单位截断数据
genre_time = genre_track.groupby('Name')['Milliseconds'].mean()
Py
Py
pd.to_timedelta(genre_time, unit='ms').dt.floor('s').sort_values()
显示结果
Name
员
员
Alternative 00:04:24
Alternative & Punk 00:03:54
Blues 00:04:30
Bossa Nova 00:03:39
Classical 00:04:53
序
序
Comedy 00:26:25
Drama 00:42:55
Easy Listening 00:03:09
Electronica/Dance 00:05:02
程
Heavy Metal
Hip Hop/Rap
00:04:57
00:02:58
程
Jazz 00:04:51
Latin 00:03:52
Metal 00:05:09
马
马
Opera 00:02:54
Pop 00:03:49
R&B/Soul 00:03:40
Reggae 00:04:07
Rock 00:04:43
黑
计算每名用户的平均消费
从三张表中获取数据,用户表获取用户id,姓名
发票表,获取发表id,用户id
发票详情表,获取发票id,单价,数量
根据用户Id('CustomerId')合并用户表和发票表,根据发票Id ('InvoiceId')合并发票和发票详情表
显示结果:
CustomerId FirstName LastName InvoiceId UnitPrice Quantity
0 1 Luís Gonçalves 98 1.99 1
1 1 Luís Gonçalves 98 1.99 1
2 1 Luís Gonçalves 121 0.99 1
3 1 Luís Gonçalves 121 0.99 1
4 1 Luís Gonçalves 121 0.99 1
计算用户每笔消费的总金额
DataFrame的assign方法 创建新列
显示结果:
按照用户Id,姓名分组,分组后对总金额求和,并排序
on
on
cols = ['CustomerId', 'FirstName', 'LastName']
cust_inv.groupby(cols)['Total'].sum().sort_values(ascending=False).head()
显示结果:
th
th
CustomerId FirstName LastName
6 Helena Holý 49.62
26 Richard Cunningham 47.62
57 Luis Rojas 46.62
Py
46
45
Hugh
Ladislav
O'Reilly
Kovács
45.62
45.62
Py
Name: Total, dtype: float64
3.3 join合并
员
员
使用join合并,可以是依据两个DataFrame的行索引,或者一个DataFrame的行索引另一个DataFrame的列索引进行数据合并
加载数据
序
序
stocks_2016 = pd.read_csv('data/stocks_2016.csv')
stocks_2017 = pd.read_csv('data/stocks_2017.csv')
stocks_2018 = pd.read_csv('data/stocks_2018.csv')
程
stocks_2016
程
显示结果:
马
马
Symbol Shares Low High
0 AAPL 80 95 110
1 TSLA 50 80 130
黑
2 WMT 40 55 70
stocks_2017
显示结果:
1 GE 100 30 40
2 IBM 87 75 95
3 SLB 20 55 85
4 TXN 500 15 23
stocks_2018
显示结果:
Symbol Shares Low High
join合并,依据两个DataFrame的行索引,如果合并的两个数据有相同的列名,需要通过lsuffix,和rsuffix,指定合并后的列名
的前缀
显示结果:
将两个DataFrame的Symbol设置为行索引,再次join数据
on
on
stocks_2016.set_index('Symbol').join(stocks_2018.set_index('Symbol'),lsuffix='_2016', rsuffix='_2018')
显示结果:
th
Symbol
将一个DataFrame的Symbol列设置为行索引,与另一个DataFrame的Symbol列进行join
Py
stocks_2016.join(stocks_2018.set_index('Symbol'),lsuffix='_2016', rsuffix='_2018',on='Symbol')
员
员
显示结果:
序
1 TSLA 50 80 130 50.0 220.0 400.0
小结
程
程
concat, join, 和merge的区别
concat :
马
马
Pandas函数
可以垂直和水平地连接两个或多个pandas对象
只用索引对齐
默认是外连接(也可以设为内连接)
黑
join :
DataFrame方法
只能水平连接两个或多个pandas对象
对齐是靠被调用的DataFrame的列索引或行索引和另一个对象的行索引(不能是列索引)
通过笛卡尔积处理重复的索引值
默认是左连接(也可以设为内连接、外连接和右连接)
merge :
DataFrame方法
只能水平连接两个DataFrame对象
对齐是靠被调用的DataFrame的列或行索引和另一个DataFrame的列或行索引
通过笛卡尔积处理重复的索引值
默认是内连接(也可以设为左连接、外连接、右连接)
07_缺失数据处理
学习目标
知道什么是缺失值,为什么会产生缺失值
熟练掌握缺失值处理的方式
1 简介
好多数据集都含缺失数据。缺失数据有多重表现形式
数据库中,缺失数据表示为NULL
在某些编程语言中用NA表示
缺失值也可能是空字符串(‘ ’)或数值
在Pandas中使用NaN表示缺失值
2 NaN简介
Pandas中的NaN值来自NumPy库,NumPy中缺失值有几种表示形式:NaN,NAN,nan,他们都一样
缺失值和其它类型的数据不同,它毫无意义,NaN不等于0,也不等于空串,
print(NaN==True)
print(NaN==False)
print(NaN==0)
print(NaN=='')
显示结果
False
False
False
on
on
False
两个NaN也不相等
print(NaN==NaN)
th
th
print(NaN==nan)
print(NaN==NAN)
print(nan==NAN)
显示结果
Py
Py
False
False
False
员
员
False
Pandas提供了isnull方法,用于测试某个值是否为缺失值
import pandas as pd
序
序
print(pd.isnull(NaN))
print(pd.isnull(nan))
print(pd.isnull(NAN))
程
显示结果
程
True
True
马
马
True
Pandas的notnull方法也可以用于判断某个值是否为缺失值
print(pd.notnull(NaN))
黑
print(pd.notnull(42))
显示结果
False
True
3 缺失值从何而来
缺失值的来源有两个
原始数据包含缺失值
数据整理过程中产生缺失值
3.1 加载包含缺失的数据
加载数据时可以通过keep_default_na 与 na_values 指定加载数据时的缺失值
print(pd.read_csv('data/survey_visited.csv'))
显示结果
ident site dated
0 619 DR-1 1927-02-08
1 622 DR-1 1927-02-10
2 734 DR-3 1939-01-07
3 735 DR-3 1930-01-12
4 751 DR-3 1930-02-26
5 752 DR-3 NaN
6 837 MSK-4 1932-01-14
7 844 DR-1 1932-03-22
加载数据,不包含默认缺失值
print(pd.read_csv('data/survey_visited.csv',keep_default_na = False))
显示结果
on
加载数据,手动指定缺失值
print(pd.read_csv('data/survey_visited.csv',na_values=[""],keep_default_na = False))
th
th
显示结果
1
2
622
734
DR-1
DR-3
1927-02-10
1939-01-07
Py
3 735 DR-3 1930-01-12
4 751 DR-3 1930-02-26
5 752 DR-3 NaN
员
员
6 837 MSK-4 1932-01-14
7 844 DR-1 1932-03-22
在merge数据的时候也会产生缺失值,在上一小节中已经提及,这里不再赘述
4 处理缺失值
序
序
程
程
马
马
黑
4.1 加载数据
加载数据
train=pd.read_csv('data/titanic_train.csv')
test=pd.read_csv('data/titanic_test.csv')
train.shape
显示结果
(891, 12)
test.shape
显示结果
(418, 11)
train.head()
显示结果
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
字段说明
PassengerId:乘客的ID
Survived:乘客是否获救,Key:0=没获救,1=已获救
Pclass:乘客船舱等级(1/2/3三个等级舱位)
Name:乘客姓名
Sex:性别
Age:年龄
SibSp:乘客在船上的兄弟姐妹/配偶数量
Parch:乘客在船上的父母/孩子数量
on
on
Ticket:船票号
Fare:船票价
Cabin:客舱号码
Embarked:登船的港口
此数据为泰坦尼克生存预测数据,Survived字段,代表该名乘客是否获救
th
th
train['Survived'].value_counts()
显示结果
Py
0 549
Py
1 342
Name: Survived, dtype: int64
员
员
第一步是检测数据集中每一列中缺失值的百分比
def missing_values_table(df):
# 计算所有的缺失值
序
序
mis_val = df.isnull().sum()
# 计算缺失值比例
mis_val_percent = 100 * df.isnull().sum() / len(df)
程
# 将结果拼接成dataframe
mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)
程
# 将列重命名
mis_val_table_ren_columns = mis_val_table.rename(
马
马
columns = {0 : '缺失值', 1 : '占比(%)'})
# 按照缺失值降序排列,并把缺失值为0的数据排除
mis_val_table_ren_columns = mis_val_table_ren_columns[
mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
黑
'占比(%)', ascending=False).round(1)
# 打印信息
print ("传入的数据集中共 " + str(df.shape[1]) + " 列.\n"
"其中 " + str(mis_val_table_ren_columns.shape[0]) +
"列包含缺失值")
# 返回缺失值信息的dataframe
return mis_val_table_ren_columns
train_missing= missing_values_table(train)
train_missing
显示结果
传入的数据集中共 12 列.
其中 3列包含缺失值
缺失值 占比(%)
Embarked 2 0.2
test_missing= missing_values_table(test)
test_missing
显示结果
传入的数据集中共 11 列.
其中 3列包含缺失值
缺失值 占比(%)
Age 86 20.6
Fare 1 0.2
训练集和测试集的缺失值比例基本相似
on
on
4.2 使用Missingno 库对缺失值进行可视化
我们可以使用Missingno来对缺失值进行可视化
使用missingno很简单,pip install missingno 安装,import missingno as msno 导入模块
th
th
import missingno as msno
msno.bar(train)
Py
显示结果
Py
<AxesSubplot:>
员
员
序
序
程
程
马
马
黑
上面的条形图提供了数据集完整性的可视化图形。 我们可以看到“年龄”,“客舱号码”和“登船的港口”列包含值缺失
缺失值位置的可视化
msno.matrix 函数 提供了快速直观的查看缺失值的分布情况
msno.matrix(train)
显示结果
<AxesSubplot:>
在有缺失值的地方,图都显示为空白。 例如,在“ Embarked”列中,只有两个丢失数据的实例,因此有两个白线。
右侧的迷你图给出了数据完整性的情况,并在底部指出了最少有10列数据是完整的,最多有12列数据是完整的
取出一小部分数据进一步查看
msno.matrix(train.sample(100))
on
on
显示结果
<AxesSubplot:>
th
th
Py
Py
员
员
序
序
数据缺失原因
程
查看缺失值之间是否具有相关性
程
msno.heatmap(train)
马
马
显示结果
<AxesSubplot:>
黑
黑
on
on
通过上图发现,age 和 cabin的相关性为0.1,(相关性取值 0 不相关,1强相关,-1强负相关),所以相关性不强,也就是说,age是
否缺失,与Cabin是否缺失没多大关系
进一步将age进行排序,验证了age的缺失 与cabin确实无关
th
th
sorted = train.sort_values('Age')
msno.matrix(sorted)
Py
显示结果
Py
<AxesSubplot:>
员
员
序
序
程
程
马
马
黑
4.3 缺失值处理
删除缺失值:删除缺失值会损失信息,并不推荐删除,当缺失数据占比较低的时候,可以尝试使用删除缺失值
按行删除:删除包含缺失值的记录,
train_1.dropna(subset=['Age'],how='any',inplace=True)
train_1['Age'].isnull().sum()
显示结果
0
train_1.shape
显示结果
(714, 12)
#从结果中看出 删除了177条数据(原始数据一共891条),按行删除会损失大量信息,只有当删除的数据量比较小的时候才会采取这种
方式
按列删除:当一列包含了很多缺失值的时候(比如超过80%),可以将该列删除,但最好不要删除数据
填充缺失值(非时间序列数据):填充缺失值是指用一个估算的值来去替代缺失数
使用常量来替换(默认值)
train_constant = train.copy()
train_constant.fillna(0,inplace = True)
train_constant.isnull().sum()
显示结果
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 0
Embarked 0
dtype: int64
train_constant[train_constant['Cabin']==0].shape
显示结果
on
on
(687, 12)
#687行 Cabin为缺失的数据被填充为0
使用统计量替换(缺失值所处列的平均值、中位数、众数)
th
th
train_mean = train.copy()
train_mean['age'].fillna(train_mean['age'].mean(),inplace = True)
train_mean.isnull().sum()
显示结果
Py
Py
PassengerId 0
Survived 0
Pclass 0
员
员
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
序
序
Ticket 0
Fare 0
Cabin 687
Embarked 2
dtype: int64
程
程
时间序列类型数据缺失值处理
我们同样可以使用pandas的fillna来处理这类情况
用时间序列中空值的上一个非空值填充
马
马
用时间序列中空值的下一个非空值填充
线性差值方法
加载时间序列数据,数据集为印度城市空气质量数据(2015-2020)
黑
city_day = pd.read_csv('data/city_day.csv',parse_dates=True,index_col='Date')
city_day1=city_day.copy()
city_day.head()
显示结果
City PM2.5 PM10 NO NO2 NOx NH3 CO SO2 O3 Benzene Toluene Xylene AQI AQI_Bucket
Date
2015-01-01 Ahmedabad NaN NaN 0.92 18.22 17.15 NaN 0.92 27.64 133.36 0.00 0.02 0.00 NaN NaN
2015-01-02 Ahmedabad NaN NaN 0.97 15.69 16.46 NaN 0.97 24.55 34.06 3.68 5.50 3.77 NaN NaN
2015-01-03 Ahmedabad NaN NaN 17.40 19.30 29.70 NaN 17.40 29.07 30.70 6.80 16.40 2.25 NaN NaN
2015-01-04 Ahmedabad NaN NaN 1.70 18.48 17.97 NaN 1.70 18.59 36.08 4.43 10.14 1.00 NaN NaN
2015-01-05 Ahmedabad NaN NaN 22.10 21.42 37.76 NaN 22.10 39.33 39.31 7.01 18.89 2.78 NaN NaN
用之前封装的方法,查看数据缺失情况
city_day_missing= missing_values_table(city_day)
city_day_missing
显示结果
传入的数据集中共 15 列.
其中 14列包含缺失值
缺失值 占比(%)
O3 4022 13.6
NO 3582 12.1
on
on
CO 2059 7.0
#查看包含缺失数据的部分
th
th
city_day['Xylene'][50:64]
显示结果
Py
Date
2015-02-20
2015-02-21
7.48
15.44
Py
2015-02-22 8.47
2015-02-23 28.46
2015-02-24 6.05
员
员
2015-02-25 0.81
2015-02-26 NaN
2015-02-27 NaN
2015-02-28 NaN
2015-03-01 1.32
序
序
2015-03-02 0.22
2015-03-03 2.25
2015-03-04 1.55
2015-03-05 4.13
程
马
city_day.fillna(method='ffill',inplace=True)
city_day['Xylene'][50:65]
显示结果
黑
Date
2015-02-20 7.48
2015-02-21 15.44
2015-02-22 8.47
2015-02-23 28.46
2015-02-24 6.05
2015-02-25 0.81
2015-02-26 0.81
2015-02-27 0.81
2015-02-28 0.81
2015-03-01 1.32
2015-03-02 0.22
2015-03-03 2.25
2015-03-04 1.55
2015-03-05 4.13
2015-03-06 4.13
Name: Xylene, dtype: float64
NaN值的前一个非空值是0.81,可以看到所有的NaN都被填充为0.81
使用bfill填充
city_day['AQI'][20:30]
显示结果
Date
2015-01-21 NaN
2015-01-22 NaN
2015-01-23 NaN
2015-01-24 NaN
2015-01-25 NaN
2015-01-26 NaN
2015-01-27 NaN
2015-01-28 NaN
2015-01-29 209.0
2015-01-30 328.0
Name: AQI, dtype: float64
city_day.fillna(method='bfill',inplace=True)
city_day['AQI'][20:30]
显示结果
Date
2015-01-21 209.0
2015-01-22 209.0
2015-01-23 209.0
2015-01-24 209.0
2015-01-25 209.0
on
on
2015-01-26 209.0
2015-01-27 209.0
2015-01-28 209.0
2015-01-29 209.0
2015-01-30 328.0
Name: AQI, dtype: float64
th
th
NaN值的后一个非空值是209,可以看到所有的NaN都被填充为209
线性差值方法填充缺失值
时间序列数据,数据随着时间的变化可能会较大。 因此,使用bfill和ffill进行插补并不是解决缺失值问题的最优方案。
Py
Py
线性插值法是一种插补缺失值技术,它假定数据点之间存在线性关系,并利用相邻数据点中的非缺失值来计算缺失数据点的
值。
city_day1['Xylene'][50:65]
员
员
显示结果
Date
2015-02-20 7.48
序
序
2015-02-21 15.44
2015-02-22 8.47
2015-02-23 28.46
2015-02-24 6.05
程
2015-02-25
2015-02-26
0.81
NaN
程
2015-02-27 NaN
2015-02-28 NaN
2015-03-01 1.32
马
马
2015-03-02 0.22
2015-03-03 2.25
2015-03-04 1.55
2015-03-05 4.13
2015-03-06 NaN
黑
city_day1.interpolate(limit_direction="both",inplace=True)
city_day1['Xylene'][50:65]
显示结果
Date
2015-02-20 7.4800
2015-02-21 15.4400
2015-02-22 8.4700
2015-02-23 28.4600
2015-02-24 6.0500
2015-02-25 0.8100
2015-02-26 0.9375
2015-02-27 1.0650
2015-02-28 1.1925
2015-03-01 1.3200
2015-03-02 0.2200
2015-03-03 2.2500
2015-03-04 1.5500
2015-03-05 4.1300
2015-03-06 2.2600
Name: Xylene, dtype: float64
其它填充缺失值的方法
除了上面介绍的填充缺失值的方法外,还可以使用机器学习算法预测来进行缺失值填充
后续课程再介绍用算法来填充缺失值的方法
小结
数据中包含缺失值是很常见的情况,缺失值可能在很多环节产生(用户没填,程序错误,数据合并...)
pandas中用np.NaN 表示缺失值,通过pd.isnull()或者pd.notnull()来判断是否是缺失值
常用的缺失值处理方式包括
删除缺失值
按行删除
按列删除
填充缺失值
默认值填充
统计值填充
时间序列数据,用前/后值填充,线性插值填充
算法填充
08_整理数据
on
on
学习目标
掌握melt函数整理数据的方法
掌握stack、unstack的用法
掌握wide_to_long函数的用法
th
th
1 melt整理数据
下面加载美国收入与宗教信仰数据,这种数据成为“宽”数据
Py
Py
import pandas as pd
pew = pd.read_csv('data/pew.csv')
pew.head()
员
员
显示结果:
0 Agnostic 27 34 60 81 76 137
序
序
1 Atheist 12 27 37 52 35 70
2 Buddhist 27 21 30 34 33 58
4 Don’t know/refused 15 14 15 11
程
10 35
对于展示数据而言,这种"宽"数据没有任何问题,如第一行数据,展示了Agnostic(不可知论(者))所有的收入分布情况
从数据分析的角度,有时候我们需要把数据由"宽"数据,转换成”长”数据
马
马
pandas的melt函数可以把宽数据集,转换为长数据集
melt及时类函数也是实力函数,也就是说既可以用pd.melt, 也可使用dataframe.melt()
黑
参数 类型 说明
使用melt对上面的pew数据集进行处理
pew_long = pd.melt(pew,id_vars='religion')
pew_long
显示结果:
religion variable value
0 Agnostic <$10k 27
1 Atheist <$10k 12
2 Buddhist <$10k 27
可以更改melt之后的数据的列名
on
on
pew_long = pd.melt(pew,id_vars='religion',var_name='income',value_name='count')
pew_long
显示结果:
th
th
religion income count
0 Agnostic <$10k 27
Py
1 Atheist
Py <$10k 12
2 Buddhist <$10k 27
员
4 Don’t know/refused <$10k 15
序
176 Other Christian Don't know/refused 18
程
179 Unaffiliated Don't know/refused 597
马
在使用melt函数转换数据的时候,也可以固定多数列,只转换少数列
bill_board = pd.read_csv('data/billboard.csv')
bill_board
黑
显示结果:
year artist track time date.entered wk1 wk2 wk3 wk4 wk5 ... wk67 wk68 wk69 wk70 wk71 wk72 wk73 wk74 wk75 wk76
0 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 87 82.0 72.0 77.0 87.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 2000 2Ge+her The Hardest Part Of ... 3:15 2000-09-02 91 87.0 92.0 NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 2000 3 Doors Down Kryptonite 3:53 2000-04-08 81 70.0 68.0 67.0 66.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 2000 3 Doors Down Loser 4:24 2000-10-21 76 76.0 72.0 69.0 67.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 2000 504 Boyz Wobble Wobble 3:35 2000-04-15 57 34.0 25.0 17.0 17.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
312 2000 Yankee Grey Another Nine Minutes 3:10 2000-04-29 86 83.0 77.0 74.0 83.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
313 2000 Yearwood, Trisha Real Live Woman 3:55 2000-04-01 85 83.0 83.0 82.0 81.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
314 2000 Ying Yang Twins Whistle While You Tw... 4:19 2000-03-18 95 94.0 91.0 85.0 84.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
315 2000 Zombie Nation Kernkraft 400 3:30 2000-09-02 99 99.0 NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
316 2000 matchbox twenty Bent 4:12 2000-04-29 60 37.0 29.0 24.0 22.0 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
使用melt 对上面数据的week进行处理,转换成长数据
bill_borad_long = pd.melt(bill_board,id_vars=['year','artist','track','time','date.entered'],
var_name='week',value_name='rating')
bill_borad_long
显示结果:
year artist track time date.entered week rating
0 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 wk1 87.0
1 2000 2Ge+her The Hardest Part Of ... 3:15 2000-09-02 wk1 91.0
24087 2000 Yankee Grey Another Nine Minutes 3:10 2000-04-29 wk76 NaN
24088 2000 Yearwood, Trisha Real Live Woman 3:55 2000-04-01 wk76 NaN
24089 2000 Ying Yang Twins Whistle While You Tw... 4:19 2000-03-18 wk76 NaN
24090 2000 Zombie Nation Kernkraft 400 3:30 2000-09-02 wk76 NaN
可以将上述数据进一步处理,当我们查询任意一首歌曲信息时,会发现数据的存储有冗余的情况
bill_borad_long[bill_borad_long.track =='Loser']
on
on
显示结果:
th
320 2000 3 Doors Down Loser 4:24 2000-10-21 wk2 76.0
员
22827 2000 3 Doors Down Loser 4:24 2000-10-21 wk73 NaN
序
23778 2000 3 Doors Down Loser 4:24 2000-10-21 wk76 NaN
76 rows × 7 columns
程
实际上,上面的数据包含了两类数据:歌曲信息、周排行信息
程
对于同一首歌曲来说,歌曲信息是完全一样的,可以考虑单独保存歌曲信息
减少上表中保存的歌曲信息,可以节省存储空间,需要完整信息的时候,可以通过merge拼接数据
我们可以把year,artist,track,time和date.entered放入一个新的dataframe中
马
billboard_songs = bill_borad_long[['year','artist','track','time','date.entered']]
billboard_songs = billboard_songs.drop_duplicates()
billboard_songs
黑
显示结果:
314 2000 Ying Yang Twins Whistle While You Tw... 4:19 2000-03-18
为上面数据添加id列
billboard_songs['id'] = range(len(billboard_songs))
billboard_songs
显示结果:
312 2000 Yankee Grey Another Nine Minutes 3:10 2000-04-29 312
313 2000 Yearwood, Trisha Real Live Woman 3:55 2000-04-01 313
on
on
314 2000 Ying Yang Twins Whistle While You Tw... 4:19 2000-03-18 314
th
317 rows × 6 columns
将id列关联到原始数据,得到包含id的完整数据,并从完整数据中,取出每周评分部分,去掉冗余信息
Py
Py
billboard_ratings = bill_borad_long.merge(billboard_songs,on=['year','artist','track','time','date.entered'])
billboard_ratings = billboard_ratings[['id','week','rating']]
billboard_ratings
显示结果:
员
员
id week rating
0 0 wk1 87.0
序
序
1 0 wk2 82.0
2 0 wk3 72.0
3 0 wk4 77.0
程
4 0 wk5 87.0
程
... ... ... ...
马
马
24087 316 wk72 NaN
这样,数据拆分成两个dataframe:billboard_songs和 billboard_ratings,保存成文件后可以减少磁盘开销,加载时可以通过merge
再还原成原始数据
billboard_songs.merge(billboard_ratings,on=['id'])
显示结果:
year artist track time date.entered id week rating
0 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0 wk1 87.0
1 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0 wk2 82.0
2 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0 wk3 72.0
3 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0 wk4 77.0
4 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0 wk5 87.0
24087 2000 matchbox twenty Bent 4:12 2000-04-29 316 wk72 NaN
24088 2000 matchbox twenty Bent 4:12 2000-04-29 316 wk73 NaN
24089 2000 matchbox twenty Bent 4:12 2000-04-29 316 wk74 NaN
24090 2000 matchbox twenty Bent 4:12 2000-04-29 316 wk75 NaN
24091 2000 matchbox twenty Bent 4:12 2000-04-29 316 wk76 NaN
2 stack整理数据
加载state_fruit数据集
on
on
state_fruit = pd.read_csv('data/state_fruit.csv', index_col=0)
state_fruit
显示结果:
th
th
Apple Orange Banana
Texas 12 10 40
Arizona 9 7 12
Py
Florida 0
Py 14 190
state_fruit.stack()
员
员
显示结果:
Texas Apple 12
Orange 10
序
序
Banana 40
Arizona Apple 9
Orange 7
Banana 12
程
Florida
Orange
Apple
14
0
程
Banana 190
dtype: int64
马
马
使用reset_index(),将结果变为DataFrame
state_fruit_tidy = state_fruit.stack().reset_index()
state_fruit_tidy
黑
显示结果:
level_0 level_1 0
0 Texas Apple 12
1 Texas Orange 10
2 Texas Banana 40
3 Arizona Apple 9
4 Arizona Orange 7
5 Arizona Banana 12
6 Florida Apple 0
7 Florida Orange 14
重命名列
state_fruit_tidy.columns = ['state', 'fruit', 'weight']
state_fruit_tidy
显示结果:
0 Texas Apple 12
1 Texas Orange 10
2 Texas Banana 40
3 Arizona Apple 9
4 Arizona Orange 7
5 Arizona Banana 12
6 Florida Apple 0
7 Florida Orange 14
也可以使用rename_axis给不同的行索引层级命名
on
on
state_fruit.stack().rename_axis(['state', 'fruit'])
显示结果:
th
th
state fruit
Texas Apple 12
Orange 10
Banana 40
Arizona Apple 9
Py
Orange
Banana
7
12
Py
Florida Apple 0
Orange 14
Banana 190
员
员
dtype: int64
再次使用reset_index方法
state_fruit.stack().rename_axis(['state', 'fruit']).reset_index(name='weight')
序
序
显示结果:
程
0 Texas Apple 12
1 Texas Orange 10
马
马
2 Texas Banana 40
3 Arizona Apple 9
4 Arizona Orange 7
黑
5 Arizona Banana 12
6 Florida Apple 0
7 Florida Orange 14
3 wide_to_long整理数据
加载数据
movie = pd.read_csv('data/movie.csv')
actor = movie[['movie_title', 'actor_1_name', 'actor_2_name', 'actor_3_name',
'actor_1_facebook_likes', 'actor_2_facebook_likes', 'actor_3_facebook_likes']]
actor.head()
显示结果:
movie_title actor_1_name actor_2_name actor_3_name actor_1_facebook_likes actor_2_facebook_likes actor_3_facebook_likes
0 Avatar CCH Pounder Joel David Moore Wes Studi 1000.0 936.0 855.0
1 Pirates of the Caribbean: At World's End Johnny Depp Orlando Bloom Jack Davenport 40000.0 5000.0 1000.0
2 Spectre Christoph Waltz Rory Kinnear Stephanie Sigman 11000.0 393.0 161.0
3 The Dark Knight Rises Tom Hardy Christian Bale Joseph Gordon-Levitt 27000.0 23000.0 23000.0
4 Star Wars: Episode VII - The Force Awakens Doug Walker Rob Walker NaN 131.0 12.0 NaN
#创建一个自定义函数,用来改变列名。将数字放到列名的最后
def change_col_name(col_name):
col_name = col_name.replace('_name', '')
if 'facebook' in col_name:
fb_idx = col_name.find('facebook')
col_name = col_name[:5] + col_name[fb_idx - 1:] + col_name[5:fb_idx-1]
return col_name
actor2 = actor.rename(columns=change_col_name)
actor2.head()
显示结果:
0 Avatar CCH Pounder Joel David Moore Wes Studi 1000.0 936.0 855.0
1 Pirates of the Caribbean: At World's End Johnny Depp Orlando Bloom Jack Davenport 40000.0 5000.0 1000.0
2 Spectre Christoph Waltz Rory Kinnear Stephanie Sigman 11000.0 393.0 161.0
on
on
3 The Dark Knight Rises Tom Hardy Christian Bale Joseph Gordon-Levitt 27000.0 23000.0 23000.0
4 Star Wars: Episode VII - The Force Awakens Doug Walker Rob Walker NaN 131.0 12.0 NaN
th
i=['movie_title'],
j='actor_num',
sep='_').reset_index()
actor2_tidy.head()
Py
显示结果:
Py
movie_title actor_num actor actor_facebook_likes
员
员
0 Avatar 1 CCH Pounder 1000.0
序
3 The Dark Knight Rises 1 Tom Hardy 27000.0
4 Star Wars: Episode VII - The Force Awakens 1 Doug Walker 131.0
程
4 unstack 处理数据
程
之前介绍了stack,unstack可以将stack的结果恢复
马
马
state_fruit = pd.read_csv('data/state_fruit.csv', index_col=0)
state_fruit.stack()
显示结果:
黑
Texas Apple 12
Orange 10
Banana 40
Arizona Apple 9
Orange 7
Banana 12
Florida Apple 0
Orange 14
Banana 190
dtype: int64
state_fruit.stack().unstack()
显示结果:
Texas 12 10 40
Arizona 9 7 12
Florida 0 14 190
melt实际上也有反向操作,melt操作在pandas文档中被称为"unpivot”, 与melt所对应的函数为piovt_table,后面的章节中再介绍
小结
melt,stack,wide_to_long函数均可以实现讲宽数据整理成长数据
melt:指定数据列,将指定列变成长数据
stack:返回一个具有多层级索引的数据,配合reset_index可以实现宽数据变成长数据
wide_to_long:处理列名带数字后缀的宽数据
stack/unstack, melt/pivot_table 互为逆向操作
09_Pandas 数据类型
学习目标
了解Numpy的特点
Pandas 数据类型转换
Pandas 分类数据类型
1 Pandas数据类型简介
1.1 Numpy 介绍
on
on
Numpy(Numerical Python)是一个开源的Python科学计算库,用于快速处理任意维度的数组。
Numpy支持常见的数组和矩阵操作。对于同样的数值计算任务,使用Numpy比直接使用Python要简洁的多。
Numpy使用ndarray对象来处理多维数组,该对象是一个快速而灵活的大数据容器。
th
th
NumPy提供了一个N维数组类型ndarray,它描述了相同类型的“items”的集合
Py
Py
员
员
序
序
用ndarray进行存储:
程
import numpy as np
程
# 创建ndarray
score = np.array([[80, 89, 86, 67, 79],
马
马
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
黑
score
显示结果
使用Python列表可以存储一维数组,通过列表的嵌套可以实现多维数组,那么为什么还需要使用Numpy的ndarray呢?
ndarray与Python原生list运算效率对比
import random
import time
import numpy as np
a = []
for i in range(100000000):
a.append(random.random())
t1 = time.time()
sum1=sum(a)
t2=time.time()
b=np.array(a)
t4=time.time()
sum3=np.sum(b)
t5=time.time()
print(t2-t1, t5-t4)
t2-t1为使用python自带的求和函数消耗的时间,t5-t4为使用numpy求和消耗的时间
显示结果
0.6686017513275146 0.1469123363494873
从结果中看到ndarray的计算速度要快很多,节约了时间
Numpy专门针对ndarray的操作和运算进行了设计,所以数组的存储效率和输入输出性能远优于Python中的嵌套列表,数组越大,
Numpy的优势就越明显。
Numpy ndarray的优势
1 数据在内存中存储的风格
on
on
th
th
Py
Py
员
员
序
ndarray在存储数据时所有元素的类型都是相同的,数据内存地址是连续的,批量操作数组元素时速度更快
序
程
程
python原生list只能通过寻址方式找到下一个元素,这虽然也导致了在通用性方面Numpy的ndarray不及Python原生list,但计算的时候速
度就慢了
2 ndarray支持并行化运算
马
马
3 Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,效率远高于纯Python代
码
ndarray的属性
属性名字 属性解释
ndarray.shape 数组维度的元组
ndarray.ndim 数组维数
ndarray.size 数组中的元素数量
ndarray.itemsize 一个数组元素的长度(字节)
ndarray.dtype 数组元素的类型
ndarray的形状
a = np.array([[1,2,3],[4,5,6]])
b = np.array([1,2,3,4])
c = np.array([[[1,2,3],[4,5,6]],[[1,2,3],[4,5,6]]])
#打印形状
a.shape
b.shape
c.shape
显示结果
(2, 3)
(4,)
(2, 2, 3)
ndarray的类型
名称 描述 简写
on
np.float16 半精度浮点数:16位,正负号1位,指数5位,精度10位 'f2'
th
np.complex64 复数,分别用两个32位浮点数表示实部和虚部 'c8'
np.object_ python对象
Py 'O'
np.unicode_ unicode类型
员
员
创建数组的时候指定类型
序
显示结果
dtype('float32')
程
程
1.3 Pandas的数据类型
Pandas 是基于Numpy的,很多功能都依赖于Numpy的ndarray实现的,Pandas的数据类型很多与Numpy类似,属性也有很多类似
马
马
黑
查看数据类型
import pandas as pd
import seaborn as sns
tips = sns.load_dataset('tips')
tips.dtypes
显示结果
total_bill float64
tip float64
sex category
smoker category
day category
time category
size int64
dtype: object
上面结果中,category数据类型表示分类变量,它与存储任意字符串的普通object数据类型不同。差异后面再讨论
2 类型转换
2.1 转换为字符串对象
在tips数据中,sex、smoker、day 和 time 变量都是category类型。通常,如果变量不是数值类型,应先将其转换成字符串类型以便
后续处理
有些数据集中可能含有id列,id的值虽然是数字,但对id进行计算(求和,求平均等)没有任何意义,在某些情况下,可能需要把它们
转换为字符串对象类型。
把一列的数据类型转换为字符串,可以使用astype方法。
tips['sex_str'] = tips['sex'].astype(str)
Python内置了str、float、int、complex和bool几种数据类型。此外还可以指定Numpy库支持的任何dtype,查看dtypes,会看到tips
on
on
多出了object类型
tips.dtypes
显示结果
th
th
total_bill float64
tip float64
sex category
Py
smoker
day
category
category
Py
time category
size int64
sex_str object
dtype: object
员
员
2.2 转换为数值类型
astype方法是通用函数,可用于把DataFrame中的任何列转换为其他dtype
序
序
可以向astype方法提供任何内置类型或numpy类型来转换列的数据类型
#把total_bill转换成字符串
tips['total_bill'] = tips['total_bill'].astype(str)
程
tips.dtypes
程
显示结果
马
马
total_bill object
tip float64
sex category
smoker category
day category
黑
time category
size int64
sex_str object
dtype: object
#把total_bill转换回float类型
tips['total_bill'] = tips['total_bill'].astype(float)
tips.dtypes
显示结果
total_bill float64
tip float64
sex category
smoker category
day category
time category
size int64
sex_str object
dtype: object
to_numeric函数
如果想把变量转换为数值类型(int,float),还可以使用pandas的to_numeric函数
DataFrame每一列的数据类型必须相同,当有些数据中有缺失,但不是NaN时(如missing,null等),会使整列数据变成字符串
类型而不是数值型,这个时候可以使用to_numeric处理
#创造包含'missing'为缺失值的数据
tips_sub_miss = tips.head(10)
tips_sub_miss.loc[[1,3,5,7],'total_bill'] = 'missing'
tips_sub_miss
显示结果
on
7 missing 3.12 Male No Sun Dinner 4
th
#查看数据类型 dtypes 会发现total_bill列变成了字符串对象类型
tips_sub_miss.dtypes
显示结果
Py
total_bill object
Py
tip float64
sex category
smoker category
员
员
day category
time category
size int64
dtype: object
序
序
对上面的数据集使用astype方法把total_bill 列转换回float类型,会抛错, Pandas 无法把'missing'转换成float
tips_sub_miss['total_bill'].astype(float)
程
显示结果
程
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
马
马
<ipython-input-8-3aba35b22fb4> in <module>
----> 1 tips_sub_miss['total_bill'].astype(float)
... ....
如果使用Pandas库中的to_numeric函数进行转换,也会得到类似的错误
pd.to_numeric(tips_sub_miss['total_bill'])
显示结果
to_numeric函数有一个参数errors,它决定了当该函数遇到无法转换的数值时该如何处理
默认情况下,该值为raise,如果to_numeric遇到无法转换的值时,会抛错
coerce: 如果to_numeric遇到无法转换的值时,会返回NaN
ignore: 如果to_numeric遇到无法转换的值时会放弃转换,什么都不做
pd.to_numeric(tips_sub_miss['total_bill'],errors = 'ignore')
显示结果
0 16.99
1 missing
2 21.01
3 missing
4 24.59
5 missing
6 8.77
7 missing
8 15.04
9 14.78
on
on
Name: total_bill, dtype: object
pd.to_numeric(tips_sub_miss['total_bill'],errors = 'coerce')
显示结果
th
th
0 16.99
1 NaN
2 21.01
Py
3
4
5
NaN
24.59
NaN
Py
6 8.77
7 NaN
8 15.04
员
员
9 14.78
Name: total_bill, dtype: float64
to_numeric向下转型
序
序
to_numeric函数还有一个downcast参数, downcast接受的参数为 'integer','signed','float','unsigned'
downcast参数设置为float之后, total_bill的数据类型由float64变为float32
pd.to_numeric(tips_sub_miss['total_bill'],errors = 'coerce',downcast='float')
程
程
显示结果
0 16.99
马
马
1 NaN
2 21.01
3 NaN
4 24.59
黑
5 NaN
6 8.77
7 NaN
8 15.04
9 14.78
Name: total_bill, dtype: float32
从上面的结果看出,转换之后的数据类型为float32, 意味着占用的内存更小了
3 分类数据(category)
Pandas 有一种类别数据, category,用于对分类值进行编码
3.1 转换为category类型
tips['sex'] = tips['sex'].astype('str')
tips.info()
显示结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 total_bill 244 non-null float64
1 tip 244 non-null float64
2 sex 244 non-null object
3 smoker 244 non-null object
4 day 244 non-null object
5 time 244 non-null object
6 size 244 non-null int64
dtypes: float64(2), int64(1), object(4)
memory usage: 13.5+ KB
tips['sex'] = tips['sex'].astype('category')
tips.info()
显示结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 total_bill 244 non-null float64
1 tip 244 non-null float64
2 sex 244 non-null category
3 smoker 244 non-null object
on
on
4 day 244 non-null object
5 time 244 non-null object
6 size 244 non-null int64
dtypes: category(1), float64(2), int64(1), object(3)
memory usage: 11.9+ KB
th
th
小结
Numpy的特点
Py
Numpy是一个高效科学计算库,Pandas的数据计算功能是对Numpy的封装
Py
ndarray是Numpy的基本数据结构,Pandas的Series和Pandas好多函数和属性都与ndarray一样
Numpy的计算效率比原生Python效率高很多,并且支持并行计算
Pandas 数据类型转换
员
可以通过as_type 和 to_numeric 函数进行数据类型转换
Pandas 分类数据类型
category类型,可以用来进行排序,并且可以自定义排序顺序
CategoricalDtype可以用来定义顺序
序
10_apply 自定义函数
序
程
程
学习目标
掌握apply的用法
马
马
知道如何创建向量化函数
1 简介
黑
Pandas提供了很多数据处理的API,但当提供的API不能满足需求的时候,需要自己编写数据处理函数, 这个时候可以使用apply函数
apply函数可以接收一个自定义函数, 可以将DataFrame的行/列数据传递给自定义函数处理
apply函数类似于编写一个for循环, 遍历行/列的每一个元素,但比使用for循环效率高很多
2 Series的apply方法
数据准备
import pandas as pd
df = pd.DataFrame({'a':[10,20,30],'b':[20,30,40]})
df
显示结果:
a b
0 10 20
1 20 30
2 30 40
创建一个自定义函数
def my_sq(x):
"""
求平方
"""
return x**2
Series有一个apply方法, 该方法有一个func参数,当传入一个函数后,apply方法就会把传入的函数应用于Series的每个元素
sq = df['a'].apply(my_sq)
sq
显示结果:
0 100
1 400
2 900
Name: a, dtype: int64
注意,把my_sq传递给apply的时候,不要加上圆括号
def my_exp(x,e):
return x**e
my_exp(2,3)
on
on
显示结果:
apply 传入 需要多个参数的函数
th
th
ex = df['a'].apply(my_exp,e=2)
ex
Py
显示结果:
Py
0 100
1 400
2 900
员
员
Name: a, dtype: int64
3 DataFrame的apply方法
序
序
把上面创建的my_sq, 直接应用到整个DataFrame中
df.apply(my_sq)
程
显示结果:
程
a b
0 100 400
马
马
1 400 900
2 900 1600
黑
dataframe是二维数据,
# 编写函数计算列的平均值
def avg_3(x,y,z):
return (x+y+z)/3
df.apply(avg_3)
显示结果:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-39-3a016d8b8964> in <module>
1 def avg_3(x,y,z):
2 return (x+y+z)/3
----> 3 df.apply(avg_3)
~\anaconda3\lib\site-packages\pandas\core\apply.py in apply_standard(self)
294 try:
295 result = libreduction.compute_reduction(
--> 296 values, self.f, axis=self.axis, dummy=dummy, labels=labels
297 )
298 except ValueError as err:
pandas\_libs\reduction.pyx in pandas._libs.reduction.compute_reduction()
pandas\_libs\reduction.pyx in pandas._libs.reduction.Reducer.get_result()
从报错的信息中看到,实际上传入avg_3函数中的只有一个变量,这个变量可以是DataFrame的行也可以是DataFrame的列, 使用apply的
时候,可以通过axis参数指定按行/ 按列 传入数据
on
# 修改avg_3函数
def avg_3_apply(col):
x = col[0]
y = col[1]
z = col[2]
return (x+y+z)/3
th
th
df.apply(avg_3_apply)
显示结果:
Py
a
b
20.0
30.0
Py
dtype: float64
# 传入axis =0 效果跟不传是一样的
员
员
df.apply(avg_3_apply,axis=0)
显示结果:
序
序
a 20.0
b 30.0
dtype: float64
程
#按行处理
程
def avg_2_apply(row):
x = row[0]
y = row[1]
马
马
return (x+y)/2
df.apply(avg_2_apply,axis=1)
显示结果:
黑
0 15.0
1 25.0
2 35.0
dtype: float64
4 apply 使用案例
接下来使用titanic数据集来介绍apply的用法
#加载数据,使用info查看该数据集的基本特征
titanic = pd.read_csv('data/titanic.csv')
titanic.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 15 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 survived 891 non-null int64
1 pclass 891 non-null int64
2 sex 891 non-null object
3 age 714 non-null float64
4 sibsp 891 non-null int64
5 parch 891 non-null int64
6 fare 891 non-null float64
7 embarked 889 non-null object
8 class 891 non-null object
9 who 891 non-null object
10 adult_male 891 non-null bool
11 deck 203 non-null object
12 embark_town 889 non-null object
13 alive 891 non-null object
14 alone 891 non-null bool
dtypes: bool(2), float64(2), int64(4), object(7)
memory usage: 92.4+ KB
缺失值数目
import numpy as np
def count_missing(vec):
"""
计算一个向量中缺失值的个数
"""
#根据值是否缺失获取一个由True/False组成的向量
null_vec = pd.isnull(vec)
on
on
# 得到null_vec中null值的个数
# null值对应True, True为1
null_count = np.sum(null_vec)
#返回向量中缺失值的个数
return null_count
th
th
缺失值占比
def prop_missing(vec):
"""
Py
向量中缺失值的占比
"""
Py
# 计算缺失值的个数
# 这里使用刚刚编写的count_missing函数
num = count_missing(vec)
员
员
#获得向量中元素的个数
#也需要统计缺失值个数
dem = vec.size
return num/dem
序
序
非缺失值占比
def prop_complete(vec):
"""
程
向量中非缺失值(完整值)的占比
"""
程
#先计算缺失值占的比例
#然后用1减去缺失值的占比
return 1-prop_missing(vec)
马
马
把前面定义好的函数应用于数据的各列
titanic.apply(count_missing)
黑
显示结果:
survived 0
pclass 0
sex 0
age 177
sibsp 0
parch 0
fare 0
embarked 2
class 0
who 0
adult_male 0
deck 688
embark_town 2
alive 0
alone 0
dtype: int64
titanic.apply(prop_missing)
显示结果:
survived 0.000000
pclass 0.000000
sex 0.000000
age 0.198653
sibsp 0.000000
parch 0.000000
fare 0.000000
embarked 0.002245
class 0.000000
who 0.000000
adult_male 0.000000
deck 0.772166
embark_town 0.002245
alive 0.000000
alone 0.000000
dtype: float64
titanic.apply(prop_complete)
显示结果:
survived 1.000000
pclass 1.000000
sex 1.000000
age 0.801347
on
on
sibsp 1.000000
parch 1.000000
fare 1.000000
embarked 0.997755
class 1.000000
who 1.000000
th
th
adult_male 1.000000
deck 0.227834
embark_town 0.997755
alive 1.000000
Py
alone
dtype: float64
1.000000
Py
按行使用
员
员
titanic.apply(count_missing,axis = 1)
显示结果:
序
序
0 1
1 0
2 1
3 0
4 1
程
..
程
886 1
887 0
888 2
889 0
马
马
890 1
Length: 891, dtype: int64
titanic.apply(prop_missing,axis = 1)
黑
显示结果:
0 0.066667
1 0.000000
2 0.066667
3 0.000000
4 0.066667
...
886 0.066667
887 0.000000
888 0.133333
889 0.000000
890 0.066667
Length: 891, dtype: float64
titanic.apply(prop_complete,axis = 1)
显示结果:
0 0.933333
1 1.000000
2 0.933333
3 1.000000
4 0.933333
...
886 0.933333
887 1.000000
888 0.866667
889 1.000000
890 0.933333
Length: 891, dtype: float64
titanic.apply(count_missing,axis = 1).value_counts()
显示结果:
1 549
0 182
2 160
dtype: int64
5 向量化函数
on
on
创建一个DataFrame
df = pd.DataFrame({'a':[10,20,30],'b':[20,30,40]})
df
th
th
显示结果:
a b
Py
0
Py 10 20
1 20 30
2 30 40
员
员
创建函数
def avg_2(x,y):
return (x+y)/2
序
序
avg_2(df['a'],df['b'])
显示结果:
程
0
1
15.0
25.0
程
2 35.0
dtype: float64
马
马
修改函数
def avg_2_mod(x,y):
if(x==20):
黑
return (np.NaN)
else:
return (x+y)/2
avg_2_mod(df['a'],df['b'])
显示结果:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-71-760ddffd820b> in <module>
----> 1 avg_2_mod(df['a'],df['b'])
<ipython-input-70-ec94cdbae859> in avg_2_mod(x, y)
1 def avg_2_mod(x,y):
----> 2 if(x==20):
3 return (np.NaN)
4 else:
5 return (x+y)/2
~\anaconda3\lib\site-packages\pandas\core\generic.py in __nonzero__(self)
1477 def __nonzero__(self):
1478 raise ValueError(
-> 1479 f"The truth value of a {type(self).__name__} is ambiguous. "
1480 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1481 )
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or
a.all().
avg_2_mod_vec = np.vectorize(avg_2_mod)
avg_2_mod_vec(df['a'],df['b'])
显示结果:
使用装饰器
@np.vectorize
def vec_avg_2_mod(x,y):
if(x==20):
return (np.NaN)
else:
return (x+y)/2
vec_avg_2_mod(df['a'],df['b'])
显示结果:
on
on
array([15., nan, 35.])
6 lambda函数
th
th
当函数比较简单的时候, 没有必要通过def 创建一个函数, 可以使用lambda表达式创建匿名函数
df.apply(lambda x: x+1)
Py
显示结果:
Py
a b
0 11 21
员
员
1 21 31
2 31 41
序
序
小结
Series和DataFrame均可以通过apply传入自定义函数
有些时候需要通过np的vectorize函数才能进行向量化计算
程
程
11_分组操作
马
马
学习目标
掌握分组操作,并对分组数据进行聚合,转换和过滤
使用内置函数和自定义函数分别执行分组操作
黑
1 聚合
在SQL中我们经常使用 GROUP BY 将某个字段,按不同的取值进行分组, 在pandas中也有groupby函数
分组之后,每组都会有至少1条数据, 将这些数据进一步处理返回单个值的过程就是聚合,比如 分组之后计算算术平均值, 或者分组之后计
算频数,都属于聚合
1.1 单变量分组聚合
在之前的课程中,我们介绍了使用Gapminder数据集分组计算平均值
df = pd.read_csv('data/gapminder.tsv',sep='\t')
df
显示结果:
country continent year lifeExp pop gdpPercap
df.groupby('year').lifeExp.mean()
显示结果:
on
on
year
1952 49.057620
1957 51.507401
1962 53.609249
th
th
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
Py
1992
1997
64.160338
65.014676
Py
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
员
员
groupby语句创建了若干组,例如上面例子中, 对year字段分组, 会将数据中不同年份作为分组结果
years = df.year.unique()
序
序
years
显示结果:
程
array([1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002,
2007], dtype=int64)
程
上面groupby 之后去平均的结果,也可以手动计算
马
马
# 针对1952年的数据取子集
y1952 = df.loc[df.year==1952,:]
y1952
黑
显示结果:
1656 West Bank and Gaza Asia 1952 43.160 1030585 1515.592329
显示结果:
49.05761971830987
groupby 语句会针对每个不同年份重复上述过程,并把所有结果放入一个DataFrame中返回
mean函数不是唯一的聚合函数, Pandas内置了许多方法, 都可以与groupby语句搭配使用
1.2 Pandas内置的聚合方法
可以与groupby一起使用的方法和函数
Pandas方法 Numpy函数 说明
size 频率统计(包含NaN值)
on
max np.max 求最大值
sum np.sum 求和
var np.var 方差
th
th
describe 计数、平均值、标准差,最小值、分位数、最大值
first 返回第一行
Py
last
Py
返回最后一行
nth 返回第N行(Python从0开始计数)
上面的结果是分组之后取平均, 也可以使用describe函数同时计算多个统计量
员
员
df.groupby('continent').lifeExp.describe()
显示结果:
序
序
continent count mean std min 25% 50% 75% max
马
1.3 聚合函数
其他库的函数
可以使用Numpy库的mean函数
黑
import numpy as np
df.groupby('continent').lifeExp.agg(np.mean)
显示结果:
continent
Africa 48.865330
Americas 64.658737
Asia 60.064903
Europe 71.903686
Oceania 74.326208
Name: lifeExp, dtype: float64
agg和 aggregate效果一样
df.groupby('continent').lifeExp.aggregate(np.mean)
显示结果:
continent
Africa 48.865330
Americas 64.658737
Asia 60.064903
Europe 71.903686
Oceania 74.326208
Name: lifeExp, dtype: float64
自定义函数
如果想在聚合的时候,使用非Pandas或其他库提供的计算, 可以自定义函数,然后再aggregate中调用它
def my_mean(values):
'''计算平均值
'''
n = len(values) # 获取数据条目数
sum = 0
for value in values:
sum += value
return(sum/n)
# 调用自定义函数
df.groupby('year').lifeExp.agg(my_mean)
显示结果:
on
on
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
th
th
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
Py
1997
2002
65.014676
65.694923
Py
2007 67.007423
Name: lifeExp, dtype: float64
员
自定义函数可以有多个参数, 第一个参数接受来自DataFrame分组这之后的值, 其余参数可自定义
# 计算全球平均预期寿命的平均值 与分组之后的平均值做差
def my_mean_diff(values,diff_value):
序
序
'''计算平均值和diff_value之差
'''
n = len(values)
sum = 0
程
马
global_mean = df.lifeExp.mean()
# 调用自定义函数 计算平均值的差值
df.groupby('year').lifeExp.agg(my_mean_diff,diff_value = global_mean)
显示结果:
黑
year
1952 -10.416820
1957 -7.967038
1962 -5.865190
1967 -3.796150
1972 -1.827053
1977 0.095718
1982 2.058758
1987 3.738173
1992 4.685899
1997 5.540237
2002 6.220483
2007 7.532983
Name: lifeExp, dtype: float64
1.4 同时传入多个函数
分组之后想计算多个聚合函数,可以把它们全部放入一个Python列表,然后把整个列表传入agg或aggregate中
# 按年计算lifeExp 的非零个数,平均值和标准差
df.groupby('year').lifeExp.agg([np.count_nonzero,np.mean,np.std])
显示结果:
count_nonzero mean std
year
on
1.5 向agg/aggregate中传入字典
分组之后,可以对多个字段用不同的方式聚合
df.groupby('year').agg({'lifeExp':'mean','pop':'median','gdpPercap':'median'})
th
th
显示结果:
year
Py
1952 49.057620 3943953.0 1968.528344
员
1962 53.609249 4686039.5 2335.439533
序
1972 57.647386 5877996.5 3339.129407
程
1987 63.212613 7774861.5 4280.300366
马
1997 65.014676 9735063.5 4781.825478
df.groupby('year').agg({'lifeExp':'mean','pop':'median','gdpPercap':'median'}).\
rename(columns={'lifeExp':'平均寿命','pop':'人口','gdpPercap':'人均Gdp'}).reset_index()
显示结果:
year 平均寿命 人口 人均Gdp
2 转换
on
on
transform 转换,需要把DataFrame中的值传递给一个函数, 而后由该函数"转换"数据。
aggregate(聚合) 返回单个聚合值,但transform 不会减少数据量
使用transform分组计算z分数
th
th
# 计算z-score x - 平均值/标准差
def my_zscore(x):
return (x-x.mean())/x.std()
#按年分组 计算z-score
Py
df.groupby('year').lifeExp.transform(my_zscore)
Py
显示结果:
员
员
0 -1.656854
1 -1.731249
2 -1.786543
3 -1.848157
4 -1.894173
序
序
...
1699 -0.081621
1700 -0.336974
1701 -1.574962
1702 -2.093346
程
1703 -1.948180
程
Name: lifeExp, Length: 1704, dtype: float64
# 查看数据集条目数, 跟之前transform处理之后的条目数一样
马
马
df.shape
显示结果:
黑
(1704, 6)
transform分组填充缺失值
之前介绍了填充缺失值的各种方法,对于某些数据集,可以使用列的平均值来填充缺失值。某些情况下,可以考虑将列进行分组,分
组之后取平均再填充缺失值
显示结果:
total_bill tip sex smoker day time size
构建缺失值
#np.random.permutation 将序列乱序
tips_10.loc[np.random.permutation(tips_10.index)[:4],'total_bill'] = np.NaN
tips_10
on
on
显示结果:
th
24 19.82 3.18 Male No Sat Dinner 2
员
192 28.44 2.56 Male Yes Thur Lunch 2
9 14.78
序
序
2
查看缺失情况
程
程
count_sex = tips_10.groupby('sex').count()
count_sex
马
马
显示结果:
Female 2 3 3 3 3 3
黑
Male 4 7 7 7 7 7
定义函数填充缺失值
def fill_na_mean(x):
# 求平均
avg = x.mean()
# 填充缺失值
return(x.fillna(avg))
total_bill_group_mean = tips_10.groupby('sex').total_bill.transform(fill_na_mean)
total_bill_group_mean
显示结果:
24 19.8200
6 8.7700
153 17.9525
211 17.9525
198 13.9300
176 17.9525
192 28.4400
124 12.4800
9 14.7800
101 15.3800
Name: total_bill, dtype: float64
将计算的结果赋值新列
tips_10['fill_total_bill'] = total_bill_group_mean
tips_10
显示结果:
on
211 NaN 5.16 Male Yes Sat Dinner 4 17.9525
th
192 28.44 2.56 Male Yes Thur Lunch 2 28.4400
transform练习
员
员
weight_loss数据集,找到减肥比赛赢家
# 加载数据
weight_loss = pd.read_csv('data/weight_loss.csv')
序
序
#查看数据
weight_loss
显示结果:
程
程
马
马
黑
黑
Name Month Week Weight
on
13 Amy Feb Week 3 177
th
17 Amy Mar Week 1 173
19 Amy
PyMar Week 2 173
员
22 Bob Mar Week 4 261
序
25 Amy Apr Week 1 170
马
30 Bob Apr Week 4 250
Bob,Amy两个人的减肥记录,从1月到4月
显示结果:
定义函数计算每周减肥比例 并测试
def find_perc_loss(s):
return abs((s - s.iloc[0]) / s.iloc[0])
#查找Bob 1月份的数据
bob_jan = weight_loss.query('Name=="Bob" and Month=="Jan"')
#测试计算减肥比例的方法
find_perc_loss(bob_jan['Weight'])
显示结果:
0 0.000000
2 0.010309
4 0.027491
6 0.027491
Name: Weight, dtype: float64
计算每周减肥比例
显示结果:
0 0.000000
1 0.000000
on
on
2 0.010309
3 0.040609
4 0.027491
5 0.040609
6 0.027491
7 0.035533
th
th
Name: Weight, dtype: float64
员
Name Month Week Weight Perc Weight Loss
序
14 Bob Feb Week 4 268 0.053
马
31 Amy Apr Week 4 161 0.053
显示结果:
6 Jan 0.027
14 Feb 0.053
22 Mar 0.026
30 Apr 0.042
显示结果:
Month Perc Weight Loss
7 Jan 0.036
15 Feb 0.089
23 Mar 0.017
31 Apr 0.053
week4_Bob.set_index('Month')-week4_Amy.set_index('Month')
显示结果:
Jan -0.009
Feb -0.036
Mar 0.009
Apr -0.011
on
on
3 过滤
使用groupby方法还可以过滤数据,调用filter 方法,传入一个返回布尔值的函数,返回False的数据会被过滤掉
使用之前的小费数据
th
th
tips = pd.read_csv('data/tips.csv')
# 查看用餐人数
tips['size'].value_counts()
Py
显示结果:
Py
2 156
3 38
4 37
员
员
5 5
6 4
1 4
Name: size, dtype: int64
序
序
结果显示,人数为1、5和6人的数据比较少,考虑将这部分数据过滤掉
程
显示结果:
马
0 16.99 1.01 Female No Sun Dinner 2
查看结果
tips_filtered['size'].value_counts()
显示结果:
2 156
3 38
4 37
Name: size, dtype: int64
4 DataFrameGroupBy对象
4.1 分组
准备数据
显示结果:
on
211 25.89 5.16 Male Yes Sat Dinner 4
th
124 12.48 2.52 Female No Thur Lunch 2
# 调用groupby 创建分组对象
grouped = tips_10.groupby('sex')
员
员
grouped
显示结果:
序
序
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000020591F63CF8>
grouped是一个DataFrameGroupBy对象,如果想查看计算过的分组,可以借助groups属性实现
grouped.groups
程
程
显示结果:
{'Female': [198, 124, 101], 'Male': [24, 6, 153, 211, 176, 192, 9]}
马
马
上面返回的结果是DataFrame的索引,实际上就是原始数据的行数
在DataFrameGroupBy对象基础上,直接就可以进行aggregate,transform计算了
黑
grouped.mean()
显示结果:
上面结果直接计算了按sex分组后,所有列的平均值,但只返回了数值列的结果,非数值列不会计算平均值
通过get_group选择分组
female = grouped.get_group('Female')
female
显示结果:
total_bill tip sex smoker day time size
4.2 遍历分组
通过groupby对象,可以遍历所有分组,相比于在groupby之后使用aggregate、transform和filter,有时候使用for循环解决问题更简
单
显示结果:
on
153 24.55 2.00 Male No Sun Dinner 4
211 25.89 5.16 Male Yes Sat Dinner 4
176 17.89 2.00 Male Yes Sun Dinner 2
192 28.44 2.56 Male Yes Thur Lunch 2
9 14.78 3.23 Male No Sun Dinner 2)
th
th
DataFrameGroupBy对象直接传入索引,会报错
grouped[0]
Py
显示结果:
Py
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
员
员
<ipython-input-75-2ce84a56ac6b> in <module>()
----> 1 grouped[0]
序
1643 )
-> 1644 return super().__getitem__(key)
1645
1646 def _gotitem(self, key, ndim: int, subset=None):
程
马
230
显示结果:
<class 'tuple'>
2
Female
<class 'str'>
total_bill tip sex smoker day time size
198 13.00 2.00 Female Yes Thur Lunch 2
124 12.48 2.52 Female No Thur Lunch 2
101 15.38 3.00 Female Yes Fri Dinner 2
<class 'pandas.core.frame.DataFrame'>
4.3 多个分组
前面使用的groupby语句只包含一个变量,可以在groupby中添加多个变量
比如上面用到的消费数据集,可以使用groupby按性别和用餐时间分别计算小费数据的平均值
group_avg = tips_10.groupby(['sex','time']).mean()
group_avg
显示结果:
sex time
on
Lunch 12.740000 2.260000 2.000000
th
分别查看分组之后结果的列名和行索引
group_avg.columns
Py
显示结果:
Py
Index(['total_bill', 'tip', 'size'], dtype='object')
员
员
group_avg.index
显示结果:
序
序
MultiIndex([('Female', 'Dinner'),
('Female', 'Lunch'),
( 'Male', 'Dinner'),
( 'Male', 'Lunch')],
程
names=['sex', 'time'])
程
可以看到,多个分组之后返回的是MultiIndex,如果想得到一个普通的DataFrame,可以在结果上调用reset_index方法
马
马
group_avg.reset_index()
显示结果:
黑
也可以在分组的时候通过as_index = False参数(默认是True),效果与调用reset_index()一样
tips_10.groupby(['sex','time'],as_index = False).mean()
显示结果:
12_数据透视表
学习目标
知道什么是透视表
熟练使用Pandas透视表(pivot_table)
1 Pandas 透视表概述
数据透视表(Pivot Table)是一种交互式的表,可以进行某些计算,如求和与计数等。所进行的计算与数据跟数据透视表中的排列有
关。
之所以称为数据透视表,是因为可以动态地改变它们的版面布置,以便按照不同方式分析数据,也可以重新安排行号、列标和页字
段。每一次改变版面布置时,数据透视表会立即按照新的布置重新计算数据。另外,如果原始数据发生更改,则可以更新数据透视
on
on
表。
在使用Excel做数据分析时,透视表是很常用的功能,Pandas也提供了同时表功能,对应的API为pivot_table
Pandas pivot_table函数介绍:pandas有两个pivot_table函数
pandas.pivot_table
pandas.DataFrame.pivot_table
th
th
pandas.pivot_table 比 pandas.DataFrame.pivot_table 多了一个参数data,data就是一个dataframe,实际上这两个函数相同
pivot_table参数中最重要的四个参数 values,index,columns,aggfunc,下面通过案例介绍pivot_tabe的使用
2 零售会员数据分析案例
Py
Py
2.1 案例业务介绍
业务背景介绍
员
员
某女鞋连锁零售企业,当前业务已线下门店为主,线上销售为辅
通过对会员的注册数据以及的分析,监控会员运营情况,为后续会员运营提供决策依据
会员等级说明
① 白银: 注册(0)
序
序
② 黄金: 下单(1~3888)
③ 铂金: 3888~6888
④ 钻石: 6888以上
程
数据分析要达成的目标
程
描述性数据分析
使用业务数据,分析出会员运营的基本情况
案例中用到的数据
马
马
① 会员信息查询.xlsx
② 会员消费报表.xlsx
③ 门店信息表.xlsx
黑
④ 全国销售订单数量表.xlsx
分析会员运营的基本情况
从量的角度分析会员运营情况:
① 整体会员运营情况(存量,增量)
② 不同渠道(线上,线下)的会员运营情况
③ 线下业务,拆解到不同的地区、门店会员运营情况
从质的角度分析会员运营情况:
① 会销比
② 连带率
③ 复购率
2.2 会员存量、增量分析
每月存量,增量是最基本的指标,通过会员数量考察会员运营情况
用到的数据:会员信息查询.xlsx
import pandas as pd
custom_info=pd.read_excel('会员信息查询.xlsx')
custom_info.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 952714 entries, 0 to 952713
Data columns (total 12 columns):
会员卡号 952714 non-null object
会员等级 952714 non-null object
会员来源 952714 non-null object
注册时间 952714 non-null datetime64[ns]
所属店铺编码 952714 non-null object
门店店员编码 253828 non-null object
省份 264801 non-null object
城市 264758 non-null object
性别 952714 non-null object
生日 785590 non-null object
年齡 952705 non-null float64
生命级别 952714 non-null object
dtypes: datetime64[ns](1), float64(1), object(10)
memory usage: 87.2+ MB
#会员信息查询
custom_info.head()
显示结果:
on
on
会员卡号 会员等级 会员来源 注册时间 所属店铺编码 门店店员编码 省份 城市 性别 生日 年齡 生命级别
0 BL6099033963550303769 黄金会员 线下扫码 2019-03-31 23:55:03.977 DPXX07 NaN NaN NaN 女 1975-04-04 43.0 活跃
1 BL6099033963450303763 黄金会员 线下扫码 2019-03-31 23:45:03.005 DPXX07 NaN NaN NaN 女 1982-04-02 36.0 活跃
2 BL6099033963464003767 白银会员 电商入口 2019-03-31 23:42:40.073 DPS00X NaN NaN NaN 女 1988-08-13 30.0 沉睡
3 BL6099033963460503766 黄金会员 线下扫码 2019-03-31 23:42:05.516 DPXX07 NaN NaN NaN 女 1993-11-24 25.0 活跃
th
th
4 BL6099033963660603765 白银会员 电商入口 2019-03-31 23:26:02.402 DPS00X NaN NaN NaN 女 1993-03-20 26.0 沉睡
需要按月统计注册的会员数量,注册时间原始数据需要处理成年-月的形式
# 给 会员信息表 添加年月列
Py
显示结果:
员
员
会员卡号 会员等级 会员来源 注册时间 注册年月
序
1 BL6099033963450303763 黄金会员 线下扫码 2019-03-31 23:45:03.005 2019-03
马
month_count = custom_info.groupby('注册年月')[['会员卡号']].count()
month_count.columns = ['月增量']
month_count.head()
显示结果:
黑
月增量
注册年月
2017-08 392910
2017-09 760
2017-10 996
2017-11 1710
2017-12 4165
用数据透视表实现相同功能:dataframe.pivot_table()
index:行索引,传入原始数据的列名
columns:列索引,传入原始数据的列名
values: 要做聚合操作的列名
aggfunc:聚合函数
显示结果:
会员卡号
注册年月
2017-08 392910
2017-09 760
2017-10 996
2017-11 1710
2017-12 4165
2018-01 15531
2018-02 13798
2018-03 49320
2018-04 71699
2018-05 27009
2018-06 17718
2018-07 8483
on
on
2018-08 109674
2018-09 147585
2018-10 14654
2018-11 9912
th
th
2018-12 6460
2019-01 9795
Py
2019-02
Py 12163
2019-03 38372
员
#通过cumsum 对月增量做累积求和
month_count.loc[:,'存量'] = month_count['月增量'].cumsum()
month_count
序
序
显示结果:
程
程
马
马
黑
黑
月增量 存量
注册年月
on
2018-08 109674 713773
th
2018-12 6460 892384
2019-02 12163
Py 914342
可视化,需要去除第一个月数据,第一个月数据是之前所有会员数量的累积(数据质量问题)
员
员
import matplotlib.pyplot as plt
month_count['月增量'].plot(figsize = (20,8),color='red',secondary_y = True)
month_count['存量'].plot.bar(figsize = (20,8),color='gray',xlabel = '年月',legend = True,ylabel = '存量')
序
序
plt.title("会员存量增量分析",fontsize=20)
显示结果:
程
程
马
马
黑
2.3 增量等级分布
会员增量存量不能真实反映会员运营的质量,需要对会员的增量存量数据做进一步拆解
从哪些维度来拆解?
从指标构成来拆解:
会员 = 白银会员+黄金会员+铂金会员+钻石会员
从业务流程来拆解:
当前案例,业务分线上、线下,又可以进一步拆解:按大区,按门店
会员等级分布分析的目的和要分析的指标
会员按照等级拆解分为:
① 白银: 注册(0)
② 黄金: 下单(1~3888)
③ 铂金: 3888~6888
④ 钻石: 6888以上
由于会员等级跟消费金额挂钩,所以会员等级分布分析可以说明会员的质量
通过groupby实现,注册年月,会员等级,按这两个字段分组,对任意字段计数
month_degree_count =custom_info.groupby(['注册年月','会员等级'])[['会员卡号']].count()
month_degree_count
显示结果:
会员卡号
注册年月 会员等级
钻石会员 185
铂金会员 387
黄金会员 15690
on
2019-02 黄金会员 8140
钻石会员 3
th
th
铂金会员 37
黄金会员 27752
Py
80 rows × 1 columns
Py
分组之后得到的是multiIndex类型的索引,将multiIndex索引变成普通索引
#使用reset_index()
员
员
month_degree_count.reset_index()
显示结果:
序
序
注册年月 会员等级 会员卡号
77 2019-03 钻石会员 3
78 2019-03 铂金会员 37
80 rows × 3 columns
#使用unstack()
month_degree_count.unstack()
显示结果:
会员卡号
注册年月
on
2018-07 3932 8 28 4515
th
2018-11 6313 4 29 3566
2019-01 3661
Py 5 9 6120
员
使用透视表实现
序
member_rating
显示结果:
程
程
马
马
黑
黑
会员等级 白银会员 钻石会员 铂金会员 黄金会员
注册年月
on
2018-08 95584 27 65 13998
th
2018-12 2808 3 29 3620
2019-02 4001
Py 5 17 8140
员
#去掉首月数据
member_rating=member_rating[1:]
pandas绘制图表
序
序
fig, ax1 = plt.subplots(figsize=(20,8),dpi=100)#构建坐标系
ax2 = ax1.twinx()#构建双胞胎坐标系
member_rating[['白银会员','黄金会员']].plot.bar(ax = ax1,rot=0,grid = True,xlabel='年月',ylabel = '白银黄
金',legend=True)
程
程
member_rating[['铂金会员','钻石会员']].plot(ax = ax2,color = ['red','gray'],ylabel='铂金钻石')
ax2.legend(loc='upper left')
plt.title("会员增量等级分布",fontsize=20)
显示结果:
马
马
黑
2.4 增量等级占比分析
增量等级占比分析,查看增量会员的消费情况
#按行求和
member_rating.loc[:,'总计'] = member_rating.sum(axis = 'columns')
#计算白银和黄金会员等级占比 铂金钻石会员数量太少暂不计算
member_rating.loc[:,'白银会员占比'] = member_rating['白银会员'].div(member_rating['总计'])
member_rating.loc[:,'黄金会员占比'] = member_rating['黄金会员'].div(member_rating['总计'])
member_rating
显示结果:
注册年月
on
2018-04 62613 48 83 8955 71699.0 0.873276 0.124897
th
2018-07 3932 8 28 4515 8483.0 0.463515 0.532241
2018-10 9093 15 34
Py 5512 14654.0 0.620513 0.376143
员
2019-01 3661 5 9 6120 9795.0 0.373762 0.624809
序
2019-03 10580 3 37 27752 38372.0 0.275722 0.723236
绘图
程
程
member_rating[['白银会员占比','黄金会员占比']].plot(color=['r','g'],ylabel='占比',figsize=(16,8),grid=True)
plt.title("会员等级占比分析",fontsize=20)
显示结果:
马
马
黑
2.5 整体等级分布
计算各个等级会员占整体的百分比
思路:按照会员等级分组,计算每组的会员数量,用每组会员数量/全部会员数量
#会员按等级分组groupby实现
ratio = custom_info.groupby('会员等级')[['会员卡号']].count()
#另一种写法
custom_info.groupby('会员等级').agg({'会员卡号':'count'})
#会员按等级分组透视表实现
ratio = custom_info.pivot_table(index = '会员等级',values = '会员卡号',aggfunc = 'count')
显示结果:
会员卡号
会员等级
白银会员 807615
钻石会员 489
铂金会员 1123
黄金会员 143487
# 计算占比
ratio.columns=['会员数']
ratio.loc[:,'占比'] = ratio['会员数'].div(ratio['会员数'].sum())
ratio
on
on
显示结果:
会员数 占比
会员等级
th
th
白银会员 807615 0.847699
铂金会员 1123
Py 0.001179
报表可视化
员
员
# autopct 显示数据标签,并指定保留小数位数
ratio.loc[['白银会员','钻石会员','黄金会员','铂金会员'],'占比'].plot.pie(figsize=
(16,8),autopct='%.1f%%',fontsize=16)
序
序
显示结果:
程
程
马
马
黑
2.6 线上线下增量分析
从业务角度,将会员数据拆分成线上和线下,比较每月线上线下会员的运营情况
将“会员来源”字段进行拆解,统计线上线下会员增量
#按会员来源进行分组 使用groupby实现
from_data = custom_info.groupby(['注册年月','会员来源'])[['会员卡号']].count()
from_data = from_data.unstack()
from_data.columns = ['电商入口', '线下扫码']
from_data = from_data[1:]
from_data
显示结果:
电商入口 线下扫码
注册年月
2017-09 1 759
2017-10 1 995
2017-11 4 1706
on
2018-08 9166 100508
th
2018-11 2350 7562
2019-02 844
Py 11319
员
# 透视表实现
custom_info.pivot_table(index = ['注册年月'],columns='会员来源',values ='会员卡号',aggfunc = 'count')
可视化
序
序
from_data.plot(figsize=(20,8),fontsize=16,grid=True)
plt.title("电商与线下会员增量分析",fontsize=18)
显示结果:
程
程
马
马
黑
2.7 地区店均会员数量
会员信息查询表中,只有店铺信息,没有地区信息,需要从门店信息表中关联地区信息
#查看门店信息表
store_info
显示结果:
店铺代 渠道 商圈等级 商圈类别 店铺 店铺 仓储 营业 仓储 店铺 店铺时 接受价格 店铺 装修
地区编码
码 大类 描述 描述 位置 状态 类别 员数 面积 等级 尚度 等级 类型 代数
1F中 店外
0 DPX00X GBL6020 DZ01 流行 中心 A 2.0 15 A 2.0 中 1.0 8代
岛 仓
1F中 店外
1 DPX002 GBL6020 DZ01 流行 中心 A 2.0 17 A 1.0 中 1.0 7代
岛 仓
2F中 店外
2 DPX003 GBL6020 DZ01 流行 中心 A 2.0 10 B 2.0 中 1.0 7代
岛 仓
1F边 店外
3 DPX004 GBL6020 DZ01 流行 新兴 A 2.0 13 B 2.0 中 1.0 7代
厅 仓
1F边 店外
4 DPX005 GBL6020 DZ01 流行 中心 A 3.0 9 B 1.0 中 1.0 7代
厅 仓
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2F边 店内
786 DPJ034 GBL6010 DZ01 流行 新兴 A 4.0 20 B 2.0 中 2.0 8代
厅 仓
1F边 店外
787 DPJ036 GBL6120 DZ01 流行 中心 A 3.0 30 B 2.0 中 2.0 8代
厅 仓
二楼 店内
788 DPJ037 GBL6100 DZ01 大众 中心 A 3.0 20 A 2.0 中 2.0 8代
边厅 仓
2F边 店内
789 DPJ038 GBL6060 DZ01 折扣 郊区 A 6.0 50 A 2.0 中 2.0 NaN
厅 仓
店内
790 DPJ04X GBL6070 DZ01 折扣 郊区 NaN A 8.0 20 C 3.0 中 2.0 8代
仓
只需要用到门店信息表中的[['店铺代码','地区编码']] 两列
on
on
store_info[['店铺代码','地区编码']].head()
显示结果:
店铺代码 地区编码
th
th
0 DPX00X GBL6020
1 DPX002 GBL6020
Py
2 DPX003
Py GBL6020
3 DPX004 GBL6020
4 DPX005 GBL6020
员
员
使用custom_info与store_info 关联,将地区编码添加到custom_info中
custom_info1 = pd.merge(custom_info,store_info[['店铺代码','地区编码']],left_on='所属店铺编码',right_on='店铺代码')
序
序
显示结果:
会员卡号 会员等级 会员来源 注册时间 所属店铺编码 门店店员编码 省份 城市 性别 生日 年齡 生命级别 注册年月 店铺代码 地区编码
0 BL6099033963550303769 黄金会员 线下扫码 2019-03-31 23:55:03.977 DPXX07 NaN NaN NaN 女 1975-04-04 43.0 活跃 2019-03 DPXX07 GBL6030
程
2
BL6099033963450303763
BL6099033963460503766
黄金会员
黄金会员
线下扫码
线下扫码
2019-03-31 23:45:03.005
2019-03-31 23:42:05.516
DPXX07
DPXX07
NaN
NaN
NaN
NaN
NaN
NaN
女
女
1982-04-02
1993-11-24
36.0
25.0
程
活跃
活跃
2019-03
2019-03
DPXX07
DPXX07
GBL6030
GBL6030
3 BL6099033963636603763 黄金会员 线下扫码 2019-03-31 23:23:26.115 DPXX07 NaN NaN NaN 女 1976-02-26 43.0 活跃 2019-03 DPXX07 GBL6030
4 BL6099033963039603755 黄金会员 线下扫码 2019-03-31 23:08:16.873 DPXX07 NaN NaN NaN 女 2000-02-07 19.0 活跃 2019-03 DPXX07 GBL6030
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
952709 HS500346JAAB 白银会员 线下扫码 2017-08-03 00:00:00.000 DPL4XX NaN 江苏省 南通市 女 1970-12-05 48.0 沉睡 2017-08 DPL4XX GBL6070
马
马
952710 HS966734JA9B 白银会员 线下扫码 2017-08-03 00:00:00.000 DPL4XX NaN 江苏省 南通市 女 NaN 0.0 沉睡 2017-08 DPL4XX GBL6070
952711 HS055703JA7C 白银会员 线下扫码 2017-08-03 00:00:00.000 DPL4XX NaN 江苏省 南通市 女 1987-06-26 31.0 沉睡 2017-08 DPL4XX GBL6070
952712 HS303634JA9C 黄金会员 电商入口 2017-08-03 00:00:00.000 DPS093 NaN 贵州省 贵阳市 女 NaN 0.0 活跃 2017-08 DPS093 GBL6D01
952713 HS670635JA96 黄金会员 电商入口 2017-08-03 00:00:00.000 DPS093 NaN NaN NaN 女 1988-01-06 31.0 活跃 2017-08 DPS093 GBL6D01
黑
显示结果:
会员数量
地区编码
GBL6010 51745
GBL6020 46945
GBL6030 112114
GBL6040 63426
GBL6050 50474
GBL6060 74447
GBL6070 147804
GBL6080 66750
GBL6090 66685
GBL6100 43695
GBL6110 48547
GBL6120 17850
on
on
GBL6130 42003
GBL6140 48894
district['店铺数'] = custom_info1[['地区编码','所属店铺编码']].drop_duplicates().groupby('地区编码')['所属店铺编
th
th
码'].count()
district
显示结果:
Py
会员数量
Py 店铺数
地区编码
GBL6010 51745 40
员
员
GBL6020 46945 41
GBL6030 112114 78
序
序
GBL6040 63426 47
GBL6050 50474 38
GBL6060 74447 31
程
GBL6090 66685 66
马
马
GBL6100 43695 38
GBL6110 48547 46
黑
GBL6120 17850 21
GBL6130 42003 71
GBL6140 48894 50
district.loc[:,'每店平均会员数']=round(district['会员数量'].div(district['店铺数']))
#计算总体平均数
district.loc[:,'总平均会员数']=district['会员数量'].sum()/district['店铺数'].sum()
#排序
district=district.sort_values(by='每店平均会员数',ascending=False)
district.head()
显示结果:
会员数量 店铺数 每店平均会员数 总平均会员数
地区编码
数据可视化
district['每店平均会员数'].plot.bar(figsize=(20,8),color='r',legend = True,grid=True)
district['总平均会员数'].plot(figsize=(20,8),color='g',legend = True,grid=True)
plt.title("地区店均会员分析",fontsize=18)
显示结果:
on
on
th
th
Py
Py
2.8 各地区会销比
员
员
会销比的计算和分析会销比的作用
会销比 = 会员消费的金额 / 全部客户消费的金额
由于数据脱敏的原因,没有全部客户消费金额的数据,所以用如下方式替换
序
序
会销比 = 会员消费的订单数 / 全部销售订单数
会销比统计的是会员消费占所有销售金额的比例
通过会销比可以衡量会员的整体质量
程
加载数据
程
custom_consume=pd.read_excel('data/会员消费报表.xlsx')
all_orders=pd.read_excel('data/全国销售订单数量表.xlsx')
马
马
custom_consume.head()
显示结果:
all_orders.head()
显示结果:
为会员消费报表添加年月列
#添加年月 这里年月要转换成整数,因为等会后面要链接的字段是整数
custom_consume.loc[:,'年月']=pd.to_datetime(custom_consume['订单日期']).apply(lambda
x:datetime.strftime(x,'%Y%m')).astype(np.int)
custom_consume.head()
显示结果:
为会员消费报表添加地区编码
custom_consume=pd.merge(custom_consume,store_info[['店铺代码','地区编码']],on='店铺代码')
custom_consume.head()
显示结果:
0 HS340766JAF6 2018-11-30 ODLOX6BXX8X2BXBBBBX 下单 DPX60X BLA267Q3X13AQM 230 1 1200.0 800 201811 GBL6140
1 BL6093996469665709064 2018-11-28 ODROX6BXX8XX28BBBB2 退单 DPX60X BLA26725X13AQTM 235 -1 -1200.0 100 201811 GBL6140
2 BL6093996469665709064 2018-11-28 ODLOX6BXX8XX28BBBBX 下单 DPX60X BLA26725X13AQTM 235 1 1200.0 900 201811 GBL6140
3 BL6093996469665709064 2018-11-27 ODROX6BXX8XX29BBBBX 退单 DPX60X BLA26725X13AQTM 235 -1 -1200.0 900 201811 GBL6140
on
on
4 BL6093996469665709064 2018-11-27 ODLOX6BXX8XX28BBBB2 下单 DPX60X BLA26725X13AQTM 235 1 1200.0 1700 201811 GBL6140
剔除电商数据,统计会员购买订单数量
# margins参数 每行每列求和
member_orders=custom_consume[custom_consume['地区编码']!='GBL6D01'].pivot_table(values = '消费数量',index='地区编
th
th
码',columns='年月',aggfunc=sum,margins=True)
member_orders
显示结果:
Py
年月
地区编码
201801 201802 201803 201804 201805 201806
Py 201807 201808 201809 201810 201811 201812 201901 All
GBL6030 1989 1503 2572 3264 4079 4023 3349 4434 5198 3499 2386 2725 3660 42681
GBL6040 432 622 671 753 938 598 662 1259 1206 516 442 404 628 9131
员
员
GBL6050 522 618 1007 1771 2420 1770 1491 1428 1218 927 744 783 1151 15850
GBL6060 431 453 600 535 756 634 651 763 707 270 167 122 156 6245
GBL6070 1018 879 1106 1203 1248 1172 1081 1993 2917 1408 1063 1129 1408 17625
GBL6090 326 342 541 629 606 436 331 1217 1564 809 646 445 440 8332
GBL6100 241 322 368 371 483 459 321 1219 1280 910 430 267 1199 7870
序
序
GBL6110 627 794 1533 1942 1012 1027 974 3435 3702 1805 1511 2328 2591 23281
GBL6120 114 125 279 253 119 49 125 488 482 259 132 178 168 2771
GBL6130 421 472 1228 1213 584 515 188 979 785 367 308 390 883 8333
GBL6140 466 526 1339 1321 589 540 1078 1824 1243 1014 850 649 1156 12595
All 6739 6770 11420 13377 13494 11904 10409 21322 22066 12046 8785 9567 14654 162553
程
程
country_sales=all_orders.pivot_table(values = '全部订单数',index='地区代码',columns='年
月',aggfunc=sum,margins=True)
country_sales
马
马
显示结果:
年月 201801 201802 201803 201804 201805 201806 201807 201808 201809 201810 201811 201812 201901 All
地区代码
GBL6010 6313 7576 7736 10455 12881 9287 8162 8151 6190 5500 5894 5313 6643 100101
黑
GBL6020 8038 10076 9776 11758 14248 10947 11335 9291 8847 6927 5618 6407 8257 121525
GBL6030 16380 16995 23606 22201 22584 17215 15608 14297 17721 15831 15377 13350 14858 226023
GBL6040 9284 12724 10448 12882 12682 10219 11490 11598 10083 9082 6787 7214 10404 134897
GBL6050 3334 4849 4443 5911 6589 5264 4483 4278 4666 3743 3926 3372 5067 59925
GBL6060 2848 3847 5225 6018 6509 4810 4594 4148 4511 3906 3295 2766 3209 55686
GBL6070 14375 17605 22083 24989 26511 21979 18659 17956 22618 19918 14078 13658 16146 250575
GBL6080 6880 6733 8870 11386 13312 11234 8989 8552 10817 10301 11182 10015 10407 128678
GBL6090 5095 6562 10346 8907 9933 8107 7304 8297 9748 9287 7405 6033 6426 103450
GBL6100 2952 3700 6221 7956 7541 6496 6096 5238 6376 5423 4385 3518 3839 69741
GBL6110 4446 5284 8699 10044 9932 7561 7055 6246 7919 5999 5605 4458 4359 87607
GBL6120 1554 2146 3517 3711 2828 2150 2203 1981 2658 2329 2042 1842 1840 30801
GBL6130 11099 10755 15119 15321 15906 11957 11113 9973 10925 14524 12886 13474 12639 165691
GBL6140 4977 5331 7729 7371 8672 6937 6450 5332 5882 7254 6671 5956 6489 85051
All 97575 114183 143818 158910 170128 134163 123541 115338 128961 120024 105151 97376 110583 1619751
计算各地区会销比
result=member_orders/country_sales
result.applymap(lambda x: format(x,".2%"))
显示结果:
年月 201801 201802 201803 201804 201805 201806 201807 201808 201809 201810 201811 201812 201901 All
地区编码
GBL6010 2.01% 0.98% 1.87% 1.08% 0.54% 0.38% 0.55% 1.01% 0.90% 1.11% 0.87% 0.81% 3.81% 1.15%
GBL6020 0.19% 0.31% 0.15% 0.04% 3.50% 5.42% 0.79% 10.55% 6.65% 1.15% 0.14% 0.86% 3.52% 2.67%
GBL6030 12.14% 8.84% 10.90% 14.70% 18.06% 23.37% 21.46% 31.01% 29.33% 22.10% 15.52% 20.41% 24.63% 18.88%
GBL6040 4.65% 4.89% 6.42% 5.85% 7.40% 5.85% 5.76% 10.86% 11.96% 5.68% 6.51% 5.60% 6.04% 6.77%
GBL6050 15.66% 12.74% 22.66% 29.96% 36.73% 33.62% 33.26% 33.38% 26.10% 24.77% 18.95% 23.22% 22.72% 26.45%
GBL6060 15.13% 11.78% 11.48% 8.89% 11.61% 13.18% 14.17% 18.39% 15.67% 6.91% 5.07% 4.41% 4.86% 11.21%
GBL6070 7.08% 4.99% 5.01% 4.81% 4.71% 5.33% 5.79% 11.10% 12.90% 7.07% 7.55% 8.27% 8.72% 7.03%
GBL6080 0.15% 0.13% 0.18% 0.04% 0.68% 0.47% 0.26% 14.28% 10.35% 1.17% 0.42% 0.49% 6.44% 2.67%
GBL6090 6.40% 5.21% 5.23% 7.06% 6.10% 5.38% 4.53% 14.67% 16.04% 8.71% 8.72% 7.38% 6.85% 8.05%
GBL6100 8.16% 8.70% 5.92% 4.66% 6.40% 7.07% 5.27% 23.27% 20.08% 16.78% 9.81% 7.59% 31.23% 11.28%
GBL6110 14.10% 15.03% 17.62% 19.33% 10.19% 13.58% 13.81% 55.00% 46.75% 30.09% 26.96% 52.22% 59.44% 26.57%
GBL6120 7.34% 5.82% 7.93% 6.82% 4.21% 2.28% 5.67% 24.63% 18.13% 11.12% 6.46% 9.66% 9.13% 9.00%
GBL6130 3.79% 4.39% 8.12% 7.92% 3.67% 4.31% 1.69% 9.82% 7.19% 2.53% 2.39% 2.89% 6.99% 5.03%
GBL6140 9.36% 9.87% 17.32% 17.92% 6.79% 7.78% 16.71% 34.21% 21.13% 13.98% 12.74% 10.90% 17.81% 14.81%
All 6.91% 5.93% 7.94% 8.42% 7.93% 8.87% 8.43% 18.49% 17.11% 10.04% 8.35% 9.82% 13.25% 10.04%
2.9 会员连带率分析
连带率的概念和为什么分析连带率
连带率是指销售的件数和交易的次数相除后的数值,反映的是顾客平均单次消费的产品件数
为什么分析连带率
连带率直接影响到客单价
连带率反应运营质量
连带率的计算
on
用到的数据:
会员消费报表.xlsx 会员消费记录
门店信息表.xlsx 建立门店地区对应关系
分析连带率的作用
通过连带率分析可以反映出人、货、场几个角度的业务问题
th
th
代码实现
统计订单的数量:需要对"订单号"去重,并且只要"下单"的数据,"退单"的不要
Py
员
显示结果:
年月 201801 201802 201803 201804 201805 201806 201807 201808 201809 201810 201811 201812 201901
地区编码
序
序
GBL6010 128 75 141 109 60 33 37 56 51 48 44 38 215
GBL6030 1761 1246 2028 2178 2932 2808 2401 3404 4102 2610 1936 2278 2908
GBL6040 436 594 642 717 909 583 617 1172 1112 485 400 385 537
GBL6050 511 583 973 1622 2263 1655 1177 1237 1120 809 709 715 967
程
GBL6060 398 418 495 468 632 541 508 620 562 229
程 138 104 105
GBL6070 892 726 909 1029 1048 923 866 1654 2266 1015 852 894 981
GBL6090 301 305 435 522 487 353 244 928 1113 662 552 354 340
马
GBL6100 234 299 307 338 408 391 280 1074 1139
马
820 394 266 1003
GBL6110 649 809 1560 1943 1016 943 810 3035 3213 1518 1344 1810 1978
GBL6120 102 129 235 227 112 48 117 418 377 236 114 142 122
GBL6130 411 451 994 1072 507 459 110 709 535 259 229 316 753
GBL6140 458 454 1127 1139 497 474 788 1377 1038 822 698 558 892
黑
统计消费商品数量
consume_count=order_data.pivot_table(values = '消费数量',index='地区编码',columns='年月',aggfunc=sum)
consume_count.head()
显示结果:
年月 201801 201802 201803 201804 201805 201806 201807 201808 201809 201810 201811 201812 201901
地区编码
GBL6030 2071 1547 2692 3374 4269 4219 3482 4621 5452 3668 2520 2834 3791
GBL6040 470 647 726 806 1008 657 718 1345 1298 572 463 431 658
GBL6050 543 637 1082 1846 2589 1906 1570 1524 1303 991 817 834 1209
计算连带率
result=comuse_count['消费数量']/order_count
#小数二位显示
result=result.applymap(lambda x:format(x,'.2f'))
result
显示结果:
年月 201801 201802 201803 201804 201805 201806 201807 201808 201809 201810 201811 201812 201901
地区编码
GBL6010 1.04 1.08 1.10 1.12 1.32 1.12 1.22 1.52 1.20 1.27 1.18 1.16 1.21
GBL6020 1.00 1.00 1.00 1.00 1.08 1.11 1.38 1.10 1.14 1.16 1.12 1.40 1.52
GBL6030 1.18 1.24 1.33 1.55 1.46 1.50 1.45 1.36 1.33 1.41 1.30 1.24 1.30
GBL6040 1.08 1.09 1.13 1.12 1.11 1.13 1.16 1.15 1.17 1.18 1.16 1.12 1.23
GBL6050 1.06 1.09 1.11 1.14 1.14 1.15 1.33 1.23 1.16 1.22 1.15 1.17 1.25
GBL6060 1.11 1.10 1.24 1.16 1.22 1.22 1.32 1.26 1.30 1.22 1.22 1.23 1.57
GBL6070 1.21 1.29 1.27 1.21 1.26 1.36 1.31 1.27 1.34 1.48 1.33 1.34 1.52
GBL6080 1.09 1.00 1.21 1.00 1.34 1.45 1.47 1.31 1.36 1.40 1.30 1.37 1.21
GBL6090 1.12 1.16 1.26 1.24 1.29 1.26 1.39 1.34 1.44 1.26 1.24 1.29 1.34
GBL6100 1.06 1.12 1.21 1.14 1.21 1.21 1.21 1.17 1.15 1.14 1.12 1.14 1.23
GBL6110 1.02 1.02 1.03 1.05 1.07 1.15 1.26 1.18 1.21 1.25 1.20 1.34 1.39
GBL6120 1.14 1.15 1.21 1.17 1.14 1.08 1.11 1.20 1.33 1.14 1.24 1.31 1.39
GBL6130 1.13 1.09 1.26 1.18 1.20 1.15 1.82 1.46 1.51 1.46 1.37 1.28 1.21
GBL6140 1.20 1.26 1.24 1.20 1.24 1.17 1.44 1.38 1.25 1.30 1.28 1.21 1.35
2.10 会员复购率分析
复购率的概念和复购率分析的作用
复购率:指会员对该品牌产品或者服务的重复购买次数,重复购买率越多,则反应出会员对品牌的忠诚度就越高,反之则越低。
计算复购率需要指定时间范围
如何计算复购:会员消费次数一天之内只计算一次
复购率 = 一段时间内消费次数大于1次的人数 / 总消费人数
复购率分析的作用:通过复购率分析可以反映出运营状态
on
on
计算步骤
统计会员消费次数与是否复购
计算复购率并定义函数
统计2018年01月~2018年12月复购率和2018年02月~2019年01月复购率
计算复购率环比
th
th
代码实现
统计会员消费次数与是否复购
由于一个会员同一天消费多次也算一次消费,所以会员消费次数按一天一次计算 因此需要对"会员卡号"和"时间"进行去重
Py
order_data=custom_consume.query("订单类型=='下单'")
Py
#因为需要用到地区编号和年月 所以选择 订单日期 卡号 年月 地区编码 四个字段一起去重
order_data=order_data[['订单日期','卡号','年月','地区编码']].drop_duplicates()
consume_count = order_data.pivot_table(index =['地区编码','卡号'],values='订单日
员
员
期',aggfunc='count').reset_index()
consume_count.rename(columns={'订单日期':'消费次数'},inplace=True)
consume_count
序
序
显示结果:
地区编码 卡号 消费次数
0 GBL6010 BL6093030369930903555 1
程
程
1 GBL6010 BL6093030394336509657 1
2 GBL6010 BL6093030394665709666 1
马
马
3 GBL6010 BL6093030394669709994 1
4 GBL6010 BL6093030396343606006 1
判断是否复购
consume_count['是否复购']=consume_count['消费次数']>1
consume_count
显示结果:
地区编码 卡号 消费次数 是否复购
计算复购率并定义函数
on
on
统计每个地区的购买人数和复购人数
consume_count.pivot_table(index = ['地区编码'],values=['消费次数','是否复购'],aggfunc={'消费次数':'count','是否
复购':'sum'})
depart_data.columns=['复购人数','购买人数']
th
th
depart_data
显示结果:
复购人数 购买人数
Py
地区编码
Py
GBL6010 64 791
员
GBL6030 2606 26395
序
GBL6050 1467 11129
程
GBL6080 127 2335
马
GBL6100 528 5546
计算复购率
depart_data.loc[:,'复购率']=depart_data['复购人数']/depart_data['购买人数']
depart_data
显示结果:
复购人数 购买人数 复购率
地区编码
on
GBL6130 5151 460 11.197826
th
上面计算的数据为所有数据的复购率,我们要统计每年的复购率,所以要先对数据进行订单日期筛选,这里我们定义一个函数
def stats_reorder(start,end,col):
"""
Py
统计指定起始年月的复购率
"""
#只要下单的数据 退单不统计
Py
order_data=custom_consume.query("订单类型=='下单'")
#筛选日期
order_data= order_data[(order_data['年月']<=end) & (order_data['年月']>=start)]
员
员
#因为需要用到地区编号和年月 所以选择 订单日期 卡号 年月 地区编码 四个字段一起去重
order_data=order_data[['订单日期','卡号','年月','地区编码']].drop_duplicates()
#按照地区编码和卡号进行分组 统计订单日期数量 就是每个地区每个会员的购买次数
consume_count = order_data.pivot_table(index =['地区编码','卡号'],values='订单日
期',aggfunc='count').reset_index()
序
序
#重命名列
consume_count.rename(columns={'订单日期':'消费次数'},inplace=True)
#判断是否复购
consume_count['是否复购']=consume_count['消费次数']>1
程
#统计每个地区的购买人数和复购人数
程
depart_data=consume_count.pivot_table(index = ['地区编码'],values=['消费次数','是否复购'],aggfunc={'消费次
数':'count','是否复购':'sum'})
#重命名列
马
马
depart_data.columns=['复购人数','购买人数']
#计算复购率
depart_data[col+'复购率']=depart_data['复购人数']/depart_data['购买人数']
return depart_data
黑
统计2018年01月~2018年12月复购率和2018年02月~2019年01月复购率
计算2018年的复购率
reorder_2018=stats_reorder(201801,201812,'2018.01-2018.12')
reorder_2018
显示结果:
复购人数 购买人数 2018.01-2018.12复购率
地区编码
on
GBL6130 415 4568 0.090849
th
计算2018年02月~2019年01月的复购率
reorder_2019=stats_reorder(201802,201901,'2018.02-2019.01')
reorder_2019
Py
显示结果:
Py
复购人数 购买人数 2018.02-2019.01复购率
员
员
地区编码
序
GBL6030 2410 25161 0.095783
程
GBL6060 391 3971 0.098464
马
GBL6080 127 2326 0.054600
计算复购率环比
#合并数据
result=pd.merge(reorder_2018[['2018.01-2018.12复购率']],reorder_2019[['2018.02-2019.01复购
率']],left_index=True,right_index=True)
#计算同比
result['同比']=(result['2018.02-2019.01复购率']-result['2018.01-2018.12复购率'])
#百分数显示
result=result.applymap(lambda x:format(x,'.2%'))
result
显示结果:
2018.01-2018.12复购率 2018.02-2019.01复购率 同比
地区编码
on
GBL6130 9.08% 8.71% -0.38%
th
小结
透视表是数据分析中经常使用的API,跟Excel中的数据透视表功能类似
Pandas的数据透视表,pivot_table,常用几个参数 index,values,columns,aggfuc,margin
Py
Pandas的功能与groupby功能类似
Py
13_datetime 数据类型
员
员
学习目标
掌握使用Pandas来处理日期时间类型数据
序
序
1 Python的datetime对象
Python内置了datetime对象,可以在datetime库中找到
程
程
from datetime import datetime
now = datetime.now()
now
马
马
显示结果:
还可以手动创建datetime
t1 = datetime.now()
t2 = datetime(2020,1,1)
diff = t1-t2
print(diff)
显示结果:
查看diff的数据类型
print(type(diff))
显示结果:
<class 'datetime.timedelta'>
2 将pandas中的数据转换成datetime
可以使用to_datetime函数把数据转换成datetime类型
#加载数据 并把Date列转换为datetime对象
ebola = pd.read_csv('data/')
#获取左上角数据
ebola.iloc[:5,:5]
显示结果:
从数据中看出 Date列是日期,但通过info查看加载后数据为object类型
ebola.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
on
on
RangeIndex: 122 entries, 0 to 121
Data columns (total 18 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 122 non-null object
1 Day 122 non-null int64
th
th
2 Cases_Guinea 93 non-null float64
3 Cases_Liberia 83 non-null float64
4 Cases_SierraLeone 87 non-null float64
5 Cases_Nigeria 38 non-null float64
Py
6
7
Cases_Senegal
Cases_UnitedStates
25 non-null
18 non-null
float64
float64
Py
8 Cases_Spain 16 non-null float64
9 Cases_Mali 12 non-null float64
10 Deaths_Guinea 92 non-null float64
11 Deaths_Liberia 81 non-null float64
员
员
12 Deaths_SierraLeone 87 non-null float64
13 Deaths_Nigeria 38 non-null float64
14 Deaths_Senegal 22 non-null float64
15 Deaths_UnitedStates 18 non-null float64
序
序
16 Deaths_Spain 16 non-null float64
17 Deaths_Mali 12 non-null float64
dtypes: float64(16), int64(1), object(1)
memory usage: 17.3+ KB
程
可以通过to_datetime方法把Date列转换为datetime,然后创建新列
程
ebola['date_dt'] = pd.to_datetime(ebola['Date'])
ebola.info()
马
马
显示结果:
<class 'pandas.core.frame.DataFrame'>
黑
ebola = pd.read_csv('data/country_timeseries.csv',parse_dates=[0])
ebola.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 122 entries, 0 to 121
Data columns (total 18 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 122 non-null datetime64[ns]
1 Day 122 non-null int64
2 Cases_Guinea 93 non-null float64
3 Cases_Liberia 83 non-null float64
4 Cases_SierraLeone 87 non-null float64
5 Cases_Nigeria 38 non-null float64
6 Cases_Senegal 25 non-null float64
7 Cases_UnitedStates 18 non-null float64
8 Cases_Spain 16 non-null float64
9 Cases_Mali 12 non-null float64
10 Deaths_Guinea 92 non-null float64
11 Deaths_Liberia 81 non-null float64
12 Deaths_SierraLeone 87 non-null float64
on
on
13 Deaths_Nigeria 38 non-null float64
14 Deaths_Senegal 22 non-null float64
15 Deaths_UnitedStates 18 non-null float64
16 Deaths_Spain 16 non-null float64
17 Deaths_Mali 12 non-null float64
dtypes: datetime64[ns](1), float64(16), int64(1)
th
th
memory usage: 17.3 KB
3 提取日期的各个部分
Py
获取了一个datetime对象,就可以提取日期的各个部分了
Py
d = pd.to_datetime('2020-06-20')
d
员
员
显示结果:
Timestamp('2020-06-20 00:00:00')
序
序
可以看到得到的数据是Timestamp类型,通过Timestamp可以获取年,月,日等部分
d.year
程
显示结果:
程
2020
马
马
d.month
显示结果:
黑
d.day
显示结果:
20
通过ebola数据集的Date列,创建新列year
ebola['year'] = ebola['Date'].dt.year
ebola['year']
显示结果:
0 2015
1 2015
2 2015
3 2015
4 2014
...
117 2014
118 2014
119 2014
120 2014
121 2014
Name: year, Length: 122, dtype: int64
ebola['month'],ebola['day'] = (ebola['Date'].dt.month,ebola['Date'].dt.day)
ebola[['Date','year','month','day']].head()
显示结果:
0 2015-01-05 2015 1 5
1 2015-01-04 2015 1 4
2 2015-01-03 2015 1 3
on
on
3 2015-01-02 2015 1 2
4 2014-12-31 2014 12 31
th
th
ebola.info()
显示结果:
Py
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 122 entries, 0 to 121
Py
Data columns (total 21 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 122 non-null datetime64[ns]
员
员
1 Day 122 non-null int64
2 Cases_Guinea 93 non-null float64
3 Cases_Liberia 83 non-null float64
4 Cases_SierraLeone 87 non-null float64
序
序
5 Cases_Nigeria 38 non-null float64
6 Cases_Senegal 25 non-null float64
7 Cases_UnitedStates 18 non-null float64
8 Cases_Spain 16 non-null float64
9 Cases_Mali 12 non-null float64
程
马
15 Deaths_UnitedStates 18 non-null float64
16 Deaths_Spain 16 non-null float64
17 Deaths_Mali 12 non-null float64
18 year 122 non-null int64
黑
4 日期运算和Timedelta
Ebola数据集中的Day列表示一个国家爆发Ebola疫情的天数。这一列数据可以通过日期运算重建该列
疫情爆发的第一天(数据集中最早的一天)是2014-03-22。计算疫情爆发的天数时,只需要用每个日期减去这个日期即可
# 获取疫情爆发的第一天
ebola['Date'].min()
显示结果:
Timestamp('2014-03-22 00:00:00')
ebola['outbreak_d'] = ebola['Date']-ebola['Date'].min()
ebola[['Date','Day','outbreak_d']].head()
显示结果:
Date Day outbreak_d
ebola[['Date','Day','outbreak_d']].tail()
显示结果:
on
121 2014-03-22 0 0 days
执行这种日期运算,会得到一个timedelta对象
ebola.info()
th
th
显示结果:
<class 'pandas.core.frame.DataFrame'>
Py
员
2 Cases_Guinea 93 non-null float64
3 Cases_Liberia 83 non-null float64
4 Cases_SierraLeone 87 non-null float64
5 Cases_Nigeria 38 non-null float64
序
序
6 Cases_Senegal 25 non-null float64
7 Cases_UnitedStates 18 non-null float64
8 Cases_Spain 16 non-null float64
9 Cases_Mali 12 non-null float64
10 Deaths_Guinea 92 non-null float64
程
11
12
Deaths_Liberia
Deaths_SierraLeone
81 non-null
87 non-null
float64
float64
程
13 Deaths_Nigeria 38 non-null float64
14 Deaths_Senegal 22 non-null float64
15 Deaths_UnitedStates 18 non-null float64
马
马
16 Deaths_Spain 16 non-null float64
17 Deaths_Mali 12 non-null float64
18 year 122 non-null int64
19 month 122 non-null int64
20 day 122 non-null int64
黑
案例
# 加载数据
banks = pd.read_csv('data/banklist.csv')
banks.head()
显示结果:
Closing Updated
Bank Name City ST CERT Acquiring Institution
Date Date
28-Apr-
2 First NBC Bank New Orleans LA 58302 Whitney Bank 26-Jul-17
17
27-Jan- 18-May-
4 Seaway Bank and Trust Company Chicago IL 19328 State Bank of Texas
17 17
可以看到有两列数据是日期时间类型,可以在导入数据的时候直接解析日期
banks = pd.read_csv('data/banklist.csv',parse_dates=[5,6])
banks.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
on
on
RangeIndex: 553 entries, 0 to 552
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Bank Name 553 non-null object
1 City 553 non-null object
th
th
2 ST 553 non-null object
3 CERT 553 non-null int64
4 Acquiring Institution 553 non-null object
5 Closing Date 553 non-null datetime64[ns]
6 Updated Date 553 non-null datetime64[ns]
Py
员
banks['closing_quarter'],banks['closing_year'] = (banks['Closing Date'].dt.quarter,banks['Closing
Date'].dt.year)
可以根据新添加的两列,计算每年破产银行数量,计算每年每季度破产银行数量
序
序
closing_year = banks.groupby(['closing_year']).size()
closing_year
程
显示结果:
程
closing_year
2000 2
马
马
2001 4
2002 11
2003 3
2004 4
2007 3
黑
2008 25
2009 140
2010 157
2011 92
2012 51
2013 24
2014 18
2015 8
2016 5
2017 6
dtype: int64
closing_year_q = banks.groupby(['closing_year','closing_quarter']).size()
可以使用绘图函数绘制结果
显示结果:
fig,ax = plt.subplots()
closing_year_q.plot()
显示结果:
on
on
th
th
5 处理股票数据
Py
Py
股票价格是包含日期的典型数据
#加载特斯拉股票数据
员
员
tesla = pd.read_csv('data/TSLA.csv')
tesla
显示结果:
序
序
Date High Low Open Close Volume Adj Close
马
1210 2020-06-10 1027.479980 982.500000 991.880005 1025.050049 18563400 1025.050049
可以看出,tesla股票数据中第一列为日期,在加载数据的时候,可以直接解析日期数据
tesla = pd.read_csv('data/TSLA.csv',parse_dates=[0])
tesla.info()
显示结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1215 entries, 0 to 1214
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 1215 non-null datetime64[ns]
1 High 1215 non-null float64
2 Low 1215 non-null float64
3 Open 1215 non-null float64
4 Close 1215 non-null float64
5 Volume 1215 non-null int64
6 Adj Close 1215 non-null float64
dtypes: datetime64[ns](1), float64(5), int64(1)
memory usage: 66.6 KB
5.1 基于日期数据获取数据子集
#获取2015年8月的股票数据
tesla.loc[(tesla.Date.dt.year ==2015) & (tesla.Date.dt.month==8)]
显示结果:
DatetimeIndex对象
on
on
在处理包含datetime的数据时,经常需要把datetime对象设置成DateFrame的索引
#首先把Date列指定为索引
tesla.index = tesla['Date']
tesla.index
th
th
显示结果:
Py
'2015-08-25', '2015-08-26', '2015-08-27', '2015-08-28',
'2015-08-31', '2015-09-01',
...
'2020-06-03', '2020-06-04', '2020-06-05', '2020-06-08',
'2020-06-09', '2020-06-10', '2020-06-11', '2020-06-12',
员
员
'2020-06-15', '2020-06-16'],
dtype='datetime64[ns]', name='Date', length=1215, freq=None)
tesla.head()
序
序
显示结果:
Date
程
2015-08-19 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000
马
2015-08-24 2015-08-24 231.399994 195.000000 202.789993 218.869995 9581600 218.869995
把索引设置为日期对象后,可以直接使用日期来获取某些数据
黑
tesla['2016'].iloc[:5]
显示结果:
Date
也可以根据年份和月份获取数据
tesla['2016-06'].iloc[:5]
显示结果:
Date High Low Open Close Volume Adj Close
Date
TimedeltaIndex对象
首先创建一个timedelta
tesla['ref_date'] = tesla['Date']-tesla['Date'].min()
tesla['ref_date']
显示结果:
Date
2015-08-19 0 days
2015-08-20 1 days
2015-08-21 2 days
2015-08-24 5 days
2015-08-25 6 days
on
on
...
2020-06-10 1757 days
2020-06-11 1758 days
2020-06-12 1759 days
2020-06-15 1762 days
th
th
2020-06-16 1763 days
Name: ref_date, Length: 1215, dtype: timedelta64[ns]
把timedelta设置为index
Py
tesla.index = tesla['ref_date']
tesla.info()
Py
显示结果:
员
员
<class 'pandas.core.frame.DataFrame'>
TimedeltaIndex: 1215 entries, 0 days to 1763 days
Data columns (total 8 columns):
# Column Non-Null Count Dtype
序
序
--- ------ -------------- -----
0 Date 1215 non-null datetime64[ns]
1 High 1215 non-null float64
2 Low 1215 non-null float64
3 Open 1215 non-null float64
程
马
memory usage: 85.4 KB
tesla.head()
黑
显示结果:
ref_date
可以基于ref_date来选择数据
显示结果:
ref_date
ebola.iloc[:,:5]
显示结果:
on
120 2014-03-24 2 86.0 NaN NaN
从上面的数据中可以看到,缺少2015年1月1日,2014年3月23日,如果想让日期连续,可以创建一个日期范围来为数据集重建索引。
th
th
可以使用date_range函数来创建连续的日期范围
head_range = pd.date_range(start='2014-12-31',end='2015-01-05')
head_range
Py
显示结果:
Py
DatetimeIndex(['2014-12-31', '2015-01-01', '2015-01-02', '2015-01-03',
'2015-01-04', '2015-01-05'],
员
员
dtype='datetime64[ns]', freq='D')
在上面的例子中,只取前5行,首先设置日期索引,然后为数据重建连续索引
ebola_5 = ebola.head()
序
序
ebola_5.index = ebola_5['Date']
ebola_5.reindex(head_range).iloc[:,:5]
显示结果:
程
程
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
马
2015-01-01 NaT NaN NaN NaN NaN
使用date_range函数创建日期序列时,可以传入一个参数freq,默认情况下freq取值为D,表示日期范围内的值是逐日递增的。
# 2020年1月1日这周所有的工作日
pd.date_range('2020-01-01','2020-01-07',freq='B')
显示结果:
从结果中看到生成的日期中缺少1月4日,1月5日,为休息日
feq参数的可能取值
Alias Description
B 工作日
C 自定义工作日
D 日历日
W 每周
M 月末
SM 月中和月末(每月第15天和月末)
BM 月末工作日
CBM 自定义月末工作日
MS 月初
SMS 月初和月中(每月第1天和第15天)
BMS 月初工作日
CBMS 自定义月初工作日
Q 季度末
on
on
BQ 季度末工作日
QS 季度初
BQS 季度初工作日
A, Y 年末
th
th
BA, BY 年末工作日
AS, YS 年初
Py
H 小时
员
员
T, min 分钟
S 秒
L, ms 毫秒
序
序
U, us microseconds
N 纳秒
程
程
在freq传入参数的基础上,可以做一些调整
# 隔一个工作日取一个工作日
马
马
pd.date_range('2020-01-01','2020-01-07',freq='2B')
显示结果:
黑
freq传入的参数可以传入多个
#2020年每个月的第一个星期四
pd.date_range('2020-01-01','2020-12-31',freq='WOM-1THU')
显示结果:
#每个月的第三个星期五
pd.date_range('2020-01-01','2020-12-31',freq='WOM-3FRI')
显示结果:
crime = pd.read_csv('data/crime.csv',parse_dates=['REPORTED_DATE'])
crime
显示结果:
crime.info()
显示结果:
on
on
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 460911 entries, 0 to 460910
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 OFFENSE_TYPE_ID 460911 non-null object
th
th
1 OFFENSE_CATEGORY_ID 460911 non-null object
2 REPORTED_DATE 460911 non-null datetime64[ns]
3 GEO_LON 457296 non-null float64
4 GEO_LAT 457296 non-null float64
Py
5
6
NEIGHBORHOOD_ID
IS_CRIME
460911 non-null
460911 non-null
object
int64
Py
7 IS_TRAFFIC 460911 non-null int64
dtypes: datetime64[ns](1), float64(2), int64(2), object(3)
memory usage: 28.1+ MB
员
员
#设置报警时间为索引
crime = crime.set_index('REPORTED_DATE')
crime.head()
序
序
查看某一天的报警记录
crime.loc['2016-05-12']
程
显示结果:
程
OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC
REPORTED_DATE
马
马
2016-05-12 23:51:00 criminal-mischief-other public-disorder -105.017241 39.705845 athmar-park 1 0
查看某一段时间的犯罪记录
crime.loc['2015-3-4':'2016-1-1'].sort_index()
显示结果:
OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC
REPORTED_DATE
时间段可以包括小时分钟
显示结果:
REPORTED_DATE
on
2015-03-04 22:32:00 traffic-accident-hit-and-run traffic-accident -104.979180 39.706613 washington-park-west 0 1
th
2016-01-01 23:16:00 traffic-accident traffic-accident -105.025088 39.707590 westwood 0 1
查询凌晨两点到五点的报警记录
Py
crime.between_time('2:00', '5:00', include_end=False)
员
员
显示结果:
REPORTED_DATE
序
序
2014-06-29 02:01:00 traffic-accident-dui-duid traffic-accident -105.000149 39.745753 cbd 0 1
马
2017-09-13 02:15:00 traffic-accident-hit-and-run traffic-accident -105.043950 39.787436 regis 0 1
查看发生在某个时刻的犯罪记录
黑
crime.at_time('5:47')
显示结果:
REPORTED_DATE
在按时间段选取数据时,可以将时间索引排序,排序之后再选取效率更高
crime_sort = crime.sort_index()
%timeit crime.loc['2015-3-4':'2016-1-1']
显示结果:
15 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit crime_sort.loc['2015-3-4':'2016-1-1']
显示结果:
1.59 ms ± 20 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
计算每周的犯罪数量
为了统计每周的犯罪数,需要按周分组
resample重采样,可以按照指定时间周期分组
crime_sort.resample('W')
#size查看分组大小
weekly_crimes = crime_sort.resample('W').size()
weekly_crimes
on
on
显示结果:
REPORTED_DATE
2012-01-08 877
2012-01-15 1071
2012-01-22 991
th
th
2012-01-29 988
2012-02-05 888
...
2017-09-03 1956
Py
2017-09-10
2017-09-17
2017-09-24
1733
1976
1839
Py
2017-10-01 1059
Freq: W-SUN, Length: 300, dtype: int64
员
员
检验分组结果
len(crime_sort.loc[:'2012-1-8'])
序
序
显示结果:
877
程
len(crime_sort.loc['2012-1-9':'2012-1-15'])
程
显示结果:
马
马
1071
也可以把周四作为每周的结束
黑
crime_sort.resample('W-THU').size()
显示结果:
REPORTED_DATE
2012-01-05 462
2012-01-12 1116
2012-01-19 924
2012-01-26 1061
2012-02-02 926
...
2017-09-07 1803
2017-09-14 1866
2017-09-21 1926
2017-09-28 1720
2017-10-05 28
Freq: W-THU, Length: 301, dtype: int64
将按周分组结果可视化
weekly_crimes.plot(figsize=(16,4),title='丹佛犯罪情况')
显示结果:
分析每季度的犯罪和交通事故数据
显示结果:
IS_CRIME IS_TRAFFIC
REPORTED_DATE
on
2012-06-30 9641 5255
th
2013-03-31 8730 4442
2013-12-31
Py 13910 4968
员
2014-09-30 17342 5734
序
2015-03-31 14989 5380
2015-12-31 16090
程 6117
马
2016-06-30 17547 5861
所有日期都是该季度的最后一天,使用QS生成每季度的第一天
crime_sort.resample('QS')['IS_CRIME', 'IS_TRAFFIC'].sum().head()
显示结果:
IS_CRIME IS_TRAFFIC
REPORTED_DATE
显示结果:
IS_CRIME 9641
IS_TRAFFIC 5255
dtype: int64
结果可视化
显示结果: on
on
分析工作日的犯罪情况:可以通过Timestamp的dt属性得到周几,然后统计
th
th
crime = pd.read_csv('data/crime.csv', parse_dates=['REPORTED_DATE'])
wd_counts = crime['REPORTED_DATE'].dt.weekday.value_counts()
wd_counts
Py
显示结果:
Py
0 70024
4 69621
员
员
2 69538
3 69287
1 68394
5 58834
序
序
6 55213
Name: REPORTED_DATE, dtype: int64
title = '丹佛犯罪和交通事故按周分析'
程
wd_counts.plot(kind='barh', title=title)
程
显示结果:
马
马
黑
小结
Pandas中,datetime64用来表示时间序列类型
时间序列类型的数据可以作为行索引,对应的数据类型是DatetimeIndex类型
datetime64类型可以做差,返回的是Timedelta类型
转换成时间序列类型后,可以按照时间的特点对数据进行处理
提取日期的各个部分(月,日,星期...)
进行日期运算
按照日期范围取值
14_Python数据可视化
学习目标
知道数据可视化的相关概念
知道Python数据可视化常用库和各自特点
能够使用Matplotlib,pandas,Seaborn进行数据可视化
了解echarts和pyecharts
能够使用pyechars绘图
1 数据可视化简介
1.1 数据可视化概念
数据可视化是指直观展现数据,它是数据处理过程的一部分。
把数值绘制出来更方便比较。借助数据可视化,能更直观地理解数据,这是直接查看数据表做不到的
数据可视化有助于揭示数据数据中隐藏的模式,数据分析时可以利用这些模式选择模型
1.2 数据可视化常用库和各自特点
Matplotlib(功能强大,代码相对复杂)
Matplotlib是Python编程语言的开源绘图库。它是Python可视化软件包中最突出的,使用最广泛的绘图工具。
on
on
Matplotlib在执行各种任务方面非常高效。可以将可视化文件导出为所有常见格式(PDF,SVG,JPG,PNG,BMP和GIF)。
Matplotlib可以创建流行的可视化类型-折线图,散点图,直方图,条形图,误差图,饼图,箱形图以及更多其他类型的图,还支
持3D绘图。
许多Python库都是基于Matplotlib构建的,Pandas和Seaborn是在Matplotlib上构建的
Matplotlib项目由John Hunter于2002年启动。Matplotlib最初是在神经生物学的博士后研究期间开始可视化癫痫患者的脑电图
(ECoG)数据。
th
th
Pandas (使用简单,功能稍弱)
Pandas的绘图功能基于Matplotlib,是对Matplotlib的二次封装
Matplotlib绘图时,代码相对复杂,使用Pandas绘制基本图表相对比较简单,更加方便
Pandas中常用的数据结构 series 和 dataframe 都有plot()方法,用于绘图
Py
Seaborn (推荐使用)
Py
Seaborn是基于Matplotlib的图形可视化python开源库
Seaborn是在Matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易
Seaborn的API设计偏向探索和理解数据
员
员
echarts 和 pyecharts (追求可视化效果,推荐使用)
序
pyecharts 是一个用Python生成 Echarts 图表的类库。
总结
数据分析的过程中可视化,推荐使用 Seaborn,理由代码简单,效果不错
追求展示效果,可以使用pyecharts,效果炫酷
程
程
2 Matplotlib绘图
2.1 Matplotlib绘图入门
马
马
Import Matplotlib
Matplotlib.pyplot 包含一系列绘图函数的相关函数
使用Matplotlib需要导入pyplot
黑
import pandas as pd
import matplotlib.pyplot as plt
Matplotlib提供了两种方法来作图:状态接口和面向对象
状态接口:
x = [-3, 5, 7] #准备数据的x轴坐标
y = [10, 2, 5] #准备数据的y轴坐标
on
th
th
2.2 matplotlib 数据可视化案例
Py
通过Anscombe数据集说明数据可视化的重要性
Py
Anscombe数据集由英国统计学家Frank Anscombe创建,数据集包含4组数据,每组数据包含两个连续变量。
每组数据的平均值、方差、相关性都相同,但是当它们可视化后,就会发现每组数据的模式明显不同。
员
员
import seaborn as sns
anscombe = sns.load_dataset('anscombe')
print(anscombe)
显示结果:
序
序
dataset x y
0 I 10.0 8.04
1 I 8.0 6.95
程
2
3
I
I
13.0
9.0
7.58
8.81
程
4 I 11.0 8.33
5 I 14.0 9.96
6 I 6.0 7.24
马
马
7 I 4.0 4.26
8 I 12.0 10.84
9 I 7.0 4.82
10 I 5.0 5.68
11 II 10.0 9.14
黑
12 II 8.0 8.14
13 II 13.0 8.74
14 II 9.0 8.77
15 II 11.0 9.26
16 II 14.0 8.10
17 II 6.0 6.13
18 II 4.0 3.10
19 II 12.0 9.13
20 II 7.0 7.26
21 II 5.0 4.74
22 III 10.0 7.46
23 III 8.0 6.77
24 III 13.0 12.74
25 III 9.0 7.11
26 III 11.0 7.81
27 III 14.0 8.84
28 III 6.0 6.08
29 III 4.0 5.39
30 III 12.0 8.15
31 III 7.0 6.42
32 III 5.0 5.73
33 IV 8.0 6.58
34 IV 8.0 5.76
35 IV 8.0 7.71
36 IV 8.0 8.84
37 IV 8.0 8.47
38 IV 8.0 7.04
39 IV 8.0 5.25
40 IV 19.0 12.50
41 IV 8.0 5.56
42 IV 8.0 7.91
43 IV 8.0 6.89
数据中的dataset 列,用来区分整个数据集中的子数据集
dataset_1 = anscombe[anscombe['dataset']=='I']
dataset_2 = anscombe[anscombe['dataset']=='II']
dataset_3 = anscombe[anscombe['dataset']=='III']
dataset_4 = anscombe[anscombe['dataset']=='IV']
查看数据的统计分布情况
dataset_1.describe()
显示结果:
x y
on
mean 9.000000 7.500909
th
25% 6.500000 6.315000
max
Py
14.000000 10.840000
dataset_2.describe()
员
员
显示结果:
x y
序
序
count 11.000000 11.000000
min 4.000000
程 3.100000
马
50% 9.000000 8.140000
dataset_3.describe()
显示结果:
x y
dataset_4.describe()
显示结果:
x y
从数据的统计量看,变量X,Y,4个子数据集的平均值和标准差基本相同,但是平均值和标准差相同,几个数据集就完全相同么?
# 创建画布
fig = plt.figure(figsize=(16,8))
# 向画布添加子图
#子图有两行两列,位置是1
axes1 = fig.add_subplot(2,2,1)
on
on
#子图有两行两列,位置是2
axes2 = fig.add_subplot(2,2,2)
#子图有两行两列,位置是3
axes3 = fig.add_subplot(2,2,3)
#子图有两行两列,位置是4
th
th
axes4 = fig.add_subplot(2,2,4)
在创建的各个坐标轴中绘制图表
Py
axes1.plot(dataset_1['x'],dataset_1['y'],'o')
axes2.plot(dataset_2['x'],dataset_2['y'],'o')
axes3.plot(dataset_3['x'],dataset_3['y'],'o')
Py
axes4.plot(dataset_4['x'],dataset_4['y'],'o')
fig
员
员
显示结果:
序
序
程
程
马
马
黑
为每个子图添加标题
axes1.set_title('dataset_1')
axes2.set_title('dataset_2')
axes3.set_title('dataset_3')
axes4.set_title('dataset_4')
fig
显示结果:
为大图添加标题
fig.suptitle('Anscombe Data')
on
on
fig
显示结果:
th
th
Py
Py
员
员
序
序
程
程
2.3 使用matplotlib绘制统计图
马
马
本小节使用seaborn 库的tips数据集,其中包含了某餐厅服务员收集的顾客付小费的相关数据
# 加载tips数据集类
tips = sns.load_dataset('tips')
黑
print(tips.head())
显示结果:
单变量
在统计学属于中,‘单变量’(univariate)指单个变量
直方图
直方图是观察单个变量最常用的方法。这些值是经过"装箱"(bin)处理的
直方图会将数据分组后绘制成图来显示变量的分布状况
fig = plt.figure()
axes1 = fig.add_subplot(1,1,1)
axes1.hist(tips['total_bill'],bins = 10)
axes1.set_title('Histogram of Total Bill')
axes1.set_xlabel('Frequency')
axes1.set_ylabel('Total Bill')
显示结果:
双变量
on
on
双变量(bivariate)指两个变量
散点图
散点图用于表示一个连续变量随另一个连续变量的变化所呈现的大致趋势
th
th
scatter_plot = plt.figure()
axes1 = scatter_plot.add_subplot(1,1,1)
axes1.scatter(tips['total_bill'],tips['tip'])
axes1.set_title('Scatterplot of Total Bill vs Tip')
axes1.set_xlabel('Total Bill')
Py
axes1.set_ylabel('Tip')
Py
显示结果:
员
员
序
序
程
程
马
马
多变量数据
二维平面可以用来展示两个变量的数据,如果是多变量,比如添加一个性别变量,可以通过不同的颜色来表示
还可以通过圆点的大小来区分变量的不同,但如果变量的大小区别不大,可能通过圆点大小来区分效果不是很好
黑
def recode_sex(sex):
if sex =='Female':
return 0
else:
return 1
tips['sex_color'] = tips['sex'].apply(recode_sex)
scatter_plot = plt.figure()
axes1 = scatter_plot.add_subplot(1,1,1)
axes1.scatter(x = tips['total_bill'],y=tips['tip'],s = tips['size']*10,c=tips['sex_color'],alpha = 0.5)
axes1.set_title('Total Bill vs Tip Colored by Sex and Sized by Size')
axes1.set_xlabel('Total Bill')
axes1.set_ylabel('Tip')
显示结果:
小结
Python常用绘图库
Matplotlib,Pandas,Seaborn,pyecharts等
Matplotlib绘图步骤
导入Matplotlib.pyplot
准备数据
创建图表,坐标轴
on
on
绘制图表
设置标题,x,y轴标题等
15_Pandas绘图
th
th
学习目标
Py
熟练掌握Pandas数据可视化常用功能
Py
1 Pandas数据可视化简介
pandas库是Python数据分析的核心库:“杀手级功能”使整个生态系统紧密结合在一起。
员
员
它不仅可以加载和转换数据,还可以做更多的事情:它还可以可视化!
易于使用且富有表现力的pandas绘图API是pandas流行的重要组成部分。
2 Pandas 单变量可视化
序
序
在本节中,我们将从最简单的可视化类型开始学习基本的Pandas绘图工具:单变量或“单变量”可视化。 包括条形图和折线图之类的基
本工具。
程
#加载数据
import pandas as pd
程
reviews = pd.read_csv("data/winemag-data_first150k.csv", index_col=0)
reviews.head(3)
马
马
显示结果:
country description designation points price province region_1 region_2 variety winery
Ripe aromas of fig, blackberry and cassis Carodorum Selección Especial Northern Tinta de Bodega Carmen
1 Spain 96 110.0 Toro NaN
are ... Reserva Spain Toro Rodríguez
2.1 柱状图和分类数据
条形图可以说是最简单的数据可视化。 在我们的案例中,将所有的葡萄酒品牌按照产区分类,看看哪个产区的葡萄酒品种多:
显示结果:
on
on
上面的图标告诉我们什么? 它说明加利福尼亚生产的葡萄酒比世界上其他任何一个省都要多! 我们可能会问加利福尼亚葡萄酒占总数
的百分之几? 此条形图告诉我们绝对数字,但是了解相对比例会更有用。 没问题:
(reviews['province'].value_counts().head(10) / len(reviews)).plot.bar(**text_kwargs)
th
th
显示结果:
Py
Py
员
员
序
序
程
程
马
马
黑
在《葡萄酒杂志》(Wine Magazine)评述的葡萄酒中,加利福尼亚生产了近三分之一!
条形图非常灵活:高度可以代表任何东西,只要它是数字即可。 每个条形可以代表任何东西,只要它是一个类别即可。
或者,就我们而言,是《葡萄酒杂志》(Wine Magazine)分配的一定分数的评论数量:
reviews['points'].value_counts().sort_index().plot.bar(**text_kwargs)
显示结果:
2.2 折线图
如果要绘制的数据不是类别值,而是连续值比较适合使用折线图
on
on
reviews['points'].value_counts().sort_index().plot.line()
显示结果:
th
th
Py
Py
员
员
柱状图和折线图区别
序
序
柱状图:简单直观,很容易根据柱子的长短看出值的大小,易于比较各组数据之间的差别
折线图:
易于比较各组数据之间的差别
能比较多组数据在同一个维度上的趋势
程
每张图上不适合展示太多折线
程
小练习:柱状图或折线图
5种不同口味冰激凌,不同口味冰激凌的销售数量。
国产轿车不同品牌的月销售数量。
马
马
学生的考试分数,范围为0-100。
2.3 面积图
面积图就是在折线图的基础上,把折线下面的面积填充颜色
黑
reviews['points'].value_counts().sort_index().plot.area()
显示结果:
当只有一个变量需要制图时,面积图和折线图之间差异不大,在这种情况下,折线图和面积图可以互相替换
2.4 直方图
直方图看起来很像条形图。 直方图是一种特殊的条形图,它可以将数据分成均匀的间隔,并用条形图显示每个间隔中有多少行。 唯一
的分析差异是,它不是代表每个值的每个条形,而是代表值的范围。
直方图缺点。 将数据分成均匀的间隔区间,所以它们对歪斜的数据的处理不是很好:
reviews['price'].plot.hist()
显示结果:
on
on
th
th
Py
Py
在第一个直方图中,将价格>200的葡萄酒排除了
员
员
在第二个直方图中,没有对价格做任何处理,由于有个别品种的酒价格极高,导致直方图的价格分布发生变化
#查看价格较高的葡萄酒情况
reviews[reviews['price'] > 1500]
序
序
显示结果:
country description designation points price province region_1 region_2 variety winery
The nose on this single-vineyard wine from a Roger Rose Arroyo Central
程
13318 US
s...
Bordeaux-style Red
Blair
马
数据倾斜:
当数据在某个维度上分布不均匀,称为数据倾斜
一共15万条数据,价格高于1500的只有三条,价格高于600的
价格高于500的只有73条数据,说明在价格这个维度上,数据的分布式不均匀的
黑
直方图适合用来展示没有数据倾斜的数据分布情况,不适合展示数据倾斜的数据
reviews.shape
显示结果:
(150930, 10)
reviews[reviews['price'] >500].shape
显示结果:
(73, 10)
对葡萄就的评分不存在数据倾斜的情况,评分数据的分布情况比较适合用直方图展示
reviews['points'].plot.hist()
显示结果:
小练习:柱状图,折线图/面积图 还是直方图
不同苹果种类(花牛,富士,国光等)在果园采摘的苹果量。
一个赛季在所有篮球比赛中的得分数。
芝加哥的公寓建筑数(按单个单元数计算)。
2.5 饼图
饼图也是一种常见的可视化形式
reviews['province'].value_counts().head(10).plot.pie()
on
on
显示结果:
th
th
Py
Py
员
员
序
序
程
程
饼图的缺陷:饼图只适合展示少量分类在整体的占比
马
马
如果分类比较多,必然每个分类的面积会比较小,这个时候很难比较两个类别
如果两个类别在饼图中彼此不相邻,很难进行比较
可以使用柱状图图来替换饼图
黑
3 Pandas 双变量可视化
在上一小结中,介绍了使用Pandas绘图,理解单个变量在数据中的互相关系,本小节会考察两个变量如何进行可视化
数据分析时,我们需要找到变量之间的相互关系,比如一个变量的增加是否与另一个变量有关,数据可视化是找到两个变量的关系的
最佳方法
3.1 散点图
最简单的两个变量可视化图形是散点图,散点图中的一个点,可以表示两个变量
显示结果:
调整图形大小,字体大小,由于pandas的绘图功能是对Matplotlib绘图功能的封装,所以很多参数pandas 和 matplotlib都一样
修改x轴 y轴标签字体
# 创建绘图区域和坐标轴
fig, axes = plt.subplots(ncols=1, figsize = (20,10))
# 使用pandas 在指定坐标轴内绘图
on
on
reviews[reviews['price'] < 100].sample(100).plot.scatter(x='price', y='points',figsize=(14,8),fontsize =
16,ax = axes)
# 通过坐标轴修改x y 标签内容和字体大小
axes.set_xlabel('price',fontdict={'fontsize':16})
显示结果:
th
th
Py
Py
员
员
序
序
程
程
马
马
上图显示了价格和评分之间有一定的相关性:也就是说,价格较高的葡萄酒通常得分更高。
请注意,我们必须对数据进行采样,从所有数据中抽取100条数据,如果将全部数据(15万条)都绘制到散点图上,会有很多点
重叠在一起,不方便观察
黑
显示结果:
on
on
由于散点图的缺点,因此散点图最适合使用相对较小的数据集以及具有大量唯一值的变量。
有几种方法可以处理过度绘图。 一:对数据进行采样。 另一种内置于熊猫的有趣方法是使用我们的下一个绘图类型,即
hexplot。
3.2 hexplot
th
th
hexplot将数据点聚合为六边形,然后根据其内的值为这些六边形上色:
显示结果:
Py
员
员
序
序
程
程
马
马
黑
上图x轴坐标缺失,属于bug,可以通过调用matplotlib的api添加x坐标
显示结果:
on
on
该图中的数据可以和散点图中的数据进行比较,但是hexplot能展示的信息更多
从hexplot中,可以看到《葡萄酒杂志》(Wine Magazine)评论的葡萄酒瓶大多数是87.5分,价格20美元
Hexplot和散点图可以应用于区间变量和/或有序分类变量的组合。
th
展示两个变量,除了使用散点图,也可以使用堆叠图
堆叠图是将一个变量绘制在另一个变量顶部的图表
接下来通过堆叠图来展示最常见的五种葡萄酒
Py
# 将葡萄酒种类分组,找到最常见的五种葡萄酒
Py
reviews.groupby(['variety'])['country'].count().sort_values(ascending = False)
显示结果:
员
员
variety
Chardonnay 14482
Pinot Noir 14288
序
序
Cabernet Sauvignon 12800
Red Blend 10061
Bordeaux-style Red Blend 7347
...
Chinuri 1
程
Petit Meslier 1
程
Espadeiro 1
Parraleta 1
Erbaluce 1
Name: country, Length: 632, dtype: int64
马
通过透视表找到每种葡萄酒中,不同评分的数量
# 透视表计数
wine_counts = temp.pivot_table(index = ['points'],columns =['variety'],aggfunc='count')['country']
# 修改列名
wine_counts.columns = ['Bordeaux-style Red Blend','Cabernet Sauvignon','Chardonnay','Pinot Noir','Red Blend']
wine_counts
显示结果:
points Bordeaux-style Red Blend Cabernet Sauvignon Chardonnay Pinot Noir Red Blend
on
93 556.0 653.0 671.0 992.0 395.0
th
97 67.0 56.0 23.0 56.0 27.0
99 8.0
Py 4.0 NaN 2.0 5.0
从上面的数据中看出,行列分别表示一个类别变量(评分,葡萄酒类别),行列交叉点表示计数,这类数据很适合用堆叠图展示
员
员
wine_counts.plot.bar(stacked=True)
显示结果:
序
序
程
程
马
马
黑
上图为堆积柱状图,适合展示少量类别的分类数据
面积堆积图:
wine_counts.plot.area()
显示结果:
面积堆积图的使用限制:
① 种类较多的数据不适合用堆积图,图中显示的数据有五个种类,比较合适,一般不要超过8个种类
② 堆积图的可解释性(读图)较差
折线图
折线图是我们已经看到的一种变量类型,当制作双变量时,它仍然非常有效。 由于此图表中的线条占用的视觉空间很小,因此在
同一图表上重叠绘制多条线条确实非常容易和有效。
wine_counts.plot.line()
显示结果:
on
on
通过这种方式使用折线图,可以突破堆叠绘图的第二个限制:可解释性。 双变量折线图更具解释性,因为折线本身不占用太多空间。
当我们并排放置多行时,它们的值仍然可读,如下所示。
例如,在这张表中,我们可以轻松地从上一个示例中回答我们的问题:哪种葡萄酒最常见的得分为87。在这里我们可以看到,绿色的
霞多丽以红色略胜于黑比诺。
小结
th
th
Pandas绘图是对Matplotlib的封装
Series和DataFrame 都有plot属性,根据不同的图形类型,调用对应的函数
Py
可以通过Matplotlib控制图片的方法来控制Pandas绘图的效果
Py
16_Seaborn 可视化
员
员
学习目标
掌握seaborn的可视化绘图方法
序
序
1 Seaborn简介
Seaborn是基于matplotlib的图形可视化python包。它提供了一种高度交互式界面,便于用户能够做出各种有吸引力的统计图表。
程
Seaborn是在matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易,在大多数情况下使用seaborn能做出很具有吸引
程
力的图,而使用matplotlib就能制作具有更多特色的图。
Seaborn和Pandas的API配合的很好,使用DataFrame/Series的数据就可以绘图
马
马
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
#加载数据 index_col 指定索引列, parse_dates=True 解析日期数据为datetime类型
黑
fifa_data = pd.read_csv('data/fifa.csv',index_col='Date',parse_dates=True)
fifa_data.head()
显示结果
seaborn 绘图
显示结果
2 Seaborn绘制单变量图
2.1 直方图
使用sns.distplot创建直方图,如下所示:
on
#加载seaborn的自带数据
tips = sns.load_dataset('tips')
#使用subplots函数创建画布,并在其中添加子图
hist,ax=plt.subplots()
# 使用seaborn的displot函数绘图
ax = sns.distplot(tips['total_bill'])
th
th
ax.set_title('Total Bill Histogram with Density Plot')
显示结果
Py
Py
员
员
序
序
程
程
displot 默认会同时绘制直方图和密度图(核密度估计 kde)。如果只想绘制直方图,可以把kde参数设置为False
核密度估计,就是采用平滑的峰值函数(“核”)来拟合观察到的数据点,从而对真实的概率分布曲线进行模拟。
马
马
hist,ax=plt.subplots()
# 使用seaborn的displot函数绘图
ax = sns.distplot(tips['total_bill'],kde = False)
ax.set_title('Total Bill Histogram')
ax.set_xlabel('Total Bill')
黑
ax.set_ylabel('Frequency')
显示结果
2.2 密度图(核密度估计)
密度图是展示单变量分布的另一种方法,本质上是通过绘制每个数据点为中心的正态分布,然后消除重叠的图,使去线下的面积为1来
创建的
hist,ax=plt.subplots()
# 使用seaborn的displot函数绘图
ax = sns.distplot(tips['total_bill'],hist = False)
ax.set_title('Total Bill Histogram')
ax.set_xlabel('Total Bill')
ax.set_ylabel('Frequency')
显示结果
如果只绘制密度图,还可以使用sns.kdeplot函数
on
on
2.3 频数图
频数图是变量分布的一维表示,常与其他图一起使用,以增强可视化效果。
下图展示的是带密度图和频数图的直方图
th
th
hist,ax=plt.subplots()
# 使用seaborn的displot函数绘图
ax = sns.distplot(tips['total_bill'],rug = True)
ax.set_title('Total Bill Histogram with Density and Rug Plot')
Py
ax.set_xlabel('Total Bill')
Py
显示结果
员
员
序
序
程
程
马
马
2.4 计数图(条形图)
计数图和直方图很像,直方图通过对数据分组来描述分布,计数图(条形图)是对离散变量(分类变量)计数。
黑
count,ax = plt.subplots()
ax = sns.countplot('day',data=tips)
ax.set_title('Count of days')
ax.set_xlabel('Day of the Week')
ax.set_ylabel('Frequency')
显示结果
3 Seaborn 双变量数据可视化
3.1 散点图
在seaborn中,创建散点图的方法有很多,但是并没有名为scatter的函数。
创建散点图可以使用regplot函数。regplot不仅可以绘制散点图,还会拟合回归线,把fit_reg设置为False,将只显示散点图
figure,ax = plt.subplots()
ax = sns.regplot(x='total_bill',y='tip',data=tips)
ax.set_title('Scatterplot of Total Bill and Tip')
ax.set_xlabel('Total Bill')
ax.set_ylabel('Tip')
显示结果
on
on
lmplot函数和regplot函数类似,也可以用于创建散点图。
lmplot函数内部会调用regplot,两者的主要区别是regplot创建坐标轴,而lmplot创建图
th
th
fig = sns.lmplot(x='total_bill',y='tip',data = tips)
显示结果
Py
Py
员
员
序
序
程
程
马
马
还可以使用jointplot在每个轴上创建包含单个变量的散点图。
显示结果
3.2 蜂巢图
on
on
使用Seaborn的jointplot绘制蜂巢图,和使用matplotlib的hexbin函数进行绘制
th
joint.fig.suptitle('Hexbin Joint Plot of Total Bill and Tip',fontsize = 10,y=1.03)
显示结果
Py
Py
员
员
序
序
程
程
马
马
黑
3.3 2D密度图
2D核密度图和kdeplot类似,但2D核密度图课展示两个变量
kde,ax = plt.subplots()
ax = sns.kdeplot(data=tips['total_bill'],data2=tips['tip'],shade=True) #是否填充轮廓
ax.set_title('Kernel Density Plot of Total Bill and Tip')
ax.set_xlabel('Total Bill')
ax.set_ylabel('Tip')
显示结果
kde,ax = plt.subplots()
ax = sns.kdeplot(data=tips['total_bill'],data2=tips['tip'],shade=False) #是否填充轮廓
ax.set_title('Kernel Density Plot of Total Bill and Tip')
ax.set_xlabel('Total Bill')
ax.set_ylabel('Tip')
显示结果
on
on
th
th
Py
Py
kde_joint = sns.jointplot(x = 'total_bill',y='tip',data=tips,kind = 'kde')
员
员
显示结果
序
序
程
程
马
马
黑
3.4 条形图
条形图也可以用于展现多个变量,barplot默认会计算平均值
import numpy as np
bar,ax = plt.subplots()
ax = sns.barplot(x='time',y='total_bill',data=tips)
ax.set_title('Bar plot of average total bill for time of day')
ax.set_xlabel('Time of day')
ax.set_ylabel('Average total bill')
显示结果
3.5 箱线图
箱线图用于显示多种统计信息:最小值,1/4分位,中位数,3/4分位,最大值,以及离群值(如果有)
box,ax = plt.subplots()
ax = sns.boxplot(x='time',y='total_bill',data = tips)
ax.set_title('Boxplot of total bill by time of day')
ax.set_xlabel('Time of day')
ax.set_ylabel('Total Bill')
on
on
显示结果
th
th
Py
Py
员
员
3.6 小提琴图
序
序
箱线图是经典的可视化方法,但可能会掩盖数据的分布,小提琴图能显示与箱线图相同的值
小提琴图把"箱线"绘成核密度估计,有助于保留数据的更多可视化信息
box,ax = plt.subplots()
程
ax = sns.violinplot(x='time',y='total_bill',data = tips)
ax.set_title('Violin plot of total bill by time of day')
程
ax.set_xlabel('Time of day')
ax.set_ylabel('Total Bill')
马
马
显示结果
黑
3.7 成对关系
当大部分数据是数值时,可以使用pairplot函数把所有成对关系绘制出来
pairplot函数会为单变量绘制直方图,双变量绘制散点图
fig = sns.pairplot(tips)
显示结果
on
on
pairplot的缺点是存在冗余信息,图的上半部分和下半部分相同
th
th
可以使用pairgrid手动指定图的上半部分和下半部分
pair_grid = sns.PairGrid(tips)
pair_grid.map_upper(sns.regplot)
Py
pair_grid.map_lower(sns.kdeplot)
pair_grid.map_diag(sns.distplot,rug=True)
Py
显示结果
员
员
序
序
程
程
马
马
黑
4 多变量数据
绘制多变量数据没有标准的套路
如果想在图中包含更多信息,可以使用颜色、大小和形状来区分它们
4.1 通过颜色区分
使用violinplot函数时,可以通过hue参数按性别(sex)给图着色
可以为“小提琴”的左右两半着不同颜色,用于区分性别
box,ax = plt.subplots()
ax = sns.violinplot(x='time',y='total_bill',hue='sex',data = tips,split = True)
ax.set_title('Violin plot of total bill by time of day')
ax.set_xlabel('Time of day')
ax.set_ylabel('Total Bill')
显示结果
其它绘图函数中也存在hue参数
on
on
scatter = sns.lmplot(x='total_bill',y='tip',data = tips,hue='sex',fit_reg = False)
显示结果
th
th
Py
Py
员
员
序
通过向hue参数传入一个类别变量,可以让pairplot变得更有意义
序
程
程
fig = sns.pairplot(tips,hue = 'sex')
显示结果
马
马
黑
黑
on
on
4.2 通过大小和形状区分
th
th
可以通过点的大小表示更多信息,但通过大小区分应谨慎使用,当大小差别不大时很难区分
在Seaborn中的lmplot,可以通过scatter_kws参数来控制散点图点的大小
Py
Py
scatter = sns.lmplot(x='total_bill',y='tip',data = tips,fit_reg=False,hue='sex',markers=['o','x'])
显示结果
员
员
序
序
程
程
马
马
黑
4.3 分面
同一张二维图形中能展示的信息有限,如果想展示更多变量,可以使用分面(facet)来满足这些需求。
使用seaborn的lmplot函数重新绘制Anscombe数据
显示结果
on
on
th
th
Py
Py
lmplot函数返回的是figure (图),但regplot 函数返回的是 axes(坐标系),只有返回figure的函数,才有col 和 col_wrap参数
如果是返回axes的函数,必须先创建FacetGrid,通过FacetGrid创建分面
员
员
#
facet = sns.FacetGrid(tips,col = 'time',size = 5)
facet.map(sns.distplot,'total_bill',rug = True)
序
序
显示结果
程
程
马
马
黑
FacetGrid中绘图可以直接调用matplotlib中的绘图方法
显示结果
on
on
th
th
Py
Py
相同的效果也可以使用seaborn的lmplot实现
员
员
fig = sns.lmplot(x = 'total_bill',y = 'tip',data =tips,fit_reg = False,hue = 'sex',col = 'day',col_wrap=2)
显示结果
序
序
程
程
马
马
黑
5 Seaborn主题和样式
上面的Seaborn图都采用了默认样式,可以使用sns.set_style函数更改样式。
该函数只要运行一次,后续绘图的样式都会发生变化
Seaborn有5中样式
darkgrid 黑色网格(默认)
whitegrid 白色网格
dark 黑色背景
white 白色背景
ticks
fig,ax = plt.subplots()
ax = sns.violinplot(x='time',y='total_bill',hue='sex',data = tips,split = True)
显示结果
on
on
通过set_style设置样式
th
th
sns.set_style('whitegrid')
fig,ax = plt.subplots()
ax = sns.violinplot(x='time',y='total_bill',hue='sex',data = tips,split = True)
Py
显示结果
Py
员
员
序
序
程
程
小结
马
马
Seaborn是对Matplotlib以及Pandas的封装,与Series,DataFrame的API配合很好
Seaborn的API非常简单
推荐使用Seaborn或Pandas进行绘图,如果需要对图形控制比较精细,可以使用Matplotlib
黑
17_Echarts和Pyecharts
学习目标
掌握pyecharts绘图
1 Echarts 和 Pyecharts简介
Echarts简介
pyecharts简介
pyechart是一个用书生成Echarts图表的Python开源类库
使用echart的绘图效果比matplotlib等更加炫酷
2 Pyecharts绘图案例
由于前面的内容基本已经介绍了常用可视化图标,和各自的特点,下面通过一个案例来介绍Pyecharts的使用
案例中使用的pyecharts版本是 1.6.0 ,pyecharts 0.X版本和 1.X版本 API变化较大,不能向下兼容,网上查资料的时候需要注意
2.1 案例数据说明
案例使用从招聘网站(拉钩、智联、前程无忧、猎聘)上爬取的一周之内数据分析在招岗位数据
分析目标:哪些公司在招聘数据分析,哪些城市数据分析的需求大,不同城市数据分析的薪资情况,数据分析对工作年限的要求,数
据分析对学历的要求
加载数据
import pandas as pd
# 加载数据
job = pd.read_csv('data/data_analysis_job.csv')
# 最近一周 python相关工作数量
job.shape
显示结果:
(7926, 9)
job.head()
显示结果:
on
on
company_name url job_name city salary experience company_area company_size description
北京盛业恒泰投资有限公司,注册资金1000万
北京盛业恒泰投资 https://jobs.51job.com/beiji 金融数据 北 6000- 金融/投资/证券,专业服务
0 NaN 500-1000人 人民币,是一家一站式金融服务的大型企业,
有限公司 ng-cyq/104869885.h... 分析师 京 18000 (咨询、人力资源、财会)
旗下有在...
专业服务(咨询、人力资 工作职责:1、协同团队完成客户委托的市场
第一象限市场咨询 https://jobs.51job.com/beiji 数据分析 北 5000-
1 1年经验 源、财会),互联网/电子商 少于50人 研究项目,包括项目的设计、执行、分析、报
(北京)有限公司 ng/122888080.html?... 师 京 8000
务 告撰写和汇报...
th
th
岗位职责:1、业务数据分析l编写月度、季
北京超思电子技术 https://jobs.51job.com/beiji 市场数据 北 7000- 电子技术/半导体/集成电
2 1年经验 500-1000人 度、年度业务数据分析报告,开展各类与业务
有限责任公司 ng/123358754.html?... 分析专员 京 10000 路,医疗设备/器械
销售数据分析...
快速消费品(食品、饮 1、根据运营总监安排,实施具体数据分析工
北京麦优尚品科贸 https://jobs.51job.com/beiji 数据分析 北 8000-
3 1年经验 料、化妆品),互联网/电子 50-150人 作。2、负责数据的审核工作,确保数据的准
有限公司 ng-cyq/79448137.ht... 员 京 10000
Py
4
北京磐程科技有限
公司
https://...
金融数据
分析师
北
京
8000-
10000
Py 1年经验
商务
金融/投资/证券 50-150人
确性。3、总...
1.人品端正,有责任心,愿与公司一同成长进
步;2.以金融行业为最后的事业并为之奋斗一
生;3....
查看数据字段
员
员
job.columns
显示结果:
序
序
Index(['company_name', 'url', 'job_name', 'city', 'salary', 'experience',
'company_area', 'company_size', 'description'],
dtype='object')
程
2.2 哪些城市在数据分析机会多
马
马
按城市分组
city_job_top20
显示结果:
city
北京 1608
上海 1176
广州 1033
深圳 820
杭州 395
成都 348
武汉 320
西安 248
南京 222
苏州 150
合肥 140
重庆 130
长沙 128
济南 123
郑州 114
太原 113
大连 94
青岛 94
东莞 83
无锡 81
Name: url, dtype: int64
pyechart 绘制柱状图
显示结果:
on
on
th
th
Py
Py
员
员
从结果中可以看出,北京上海广州深圳等一线城市,对数据分析的需求最为旺盛
序
序
2.3 哪些公司在招聘Python程序员
根据公司名字对数据进行分组
程
马
显示结果:
company_name
北京字节跳动科技有限公司 213
武汉益越商务信息咨询有限公司 85
黑
北京融汇天诚投资管理有限公司 81
字节跳动 67
腾讯科技(深圳)有限公司 43
...
北京华融泰克科技有限公司 7
广发银行股份有限公司信用卡中心 7
软通动力信息技术(集团)有限公司 7
中国平安人寿保险股份有限公司 7
墨博(湖南)文化传媒有限公司 7
Name: url, Length: 100, dtype: int64
pyecharts绘制词云图
显示结果:
on
on
2.4 岗位数量与平均起薪分布
th
th
数据清洗,提取起薪
# 数据清洗 提取起薪
import re
Py
def get_salary_down(x):
if ('面议' in x) or ('小时' in x) or ('以' in x):
return 0
员
员
elif '元' in x:
return int(re.search(pattern="([0-9]+)-([0-9]+)元/月", string=x).group(1))
elif '-' in x:
return int(re.search(pattern="([0-9]+)-([0-9]+)", string=x).group(1))
else:
序
序
return int(x)
提取起薪数据之前,首先处理缺失值,将缺失数据去掉并提取起薪
程
显示结果:
马
0 141
1000 5
1500 8
黑
1800 1
2000 79
...
60000 3
62500 1
70000 2
80000 1
100000 1
Name: salary_down, Length: 92, dtype: int64
数据异常值处理
从处理的起薪中发现,有一些异常数据
特别低的:小于3000
特别高的:大于80000
异常值处理:直接删除
job_salary = job[job['salary_down']>3000]
job_salary = job_salary[job_salary['salary_down']<80000]
绘制气泡图数据准备
将所有城市的薪资和就业岗位数合并到一起
def prepare_data(data):
# 取出各城市有多少岗位
temp1 = data.groupby('city')['url'].count().sort_values(ascending = False)
# 计算出各城市岗位的平均起薪
temp = data.groupby('city')['salary_down'].mean().sort_values(ascending = False)
# 合并数据
temp2 = pd.concat([temp,temp1],axis = 1)
temp2 = temp2.reset_index()
return temp2
处理数据
salary_data = prepare_data(job)
salary_data.columns = ['city','salary_down','job_count']
salary_data.head()
显示结果:
0 北京 13882.837209 1505
1 上海 10795.126005 1119
2 深圳 10305.392258 775
on
on
3 杭州 9756.156425 358
4 广州 7819.732283 889
气泡图展示数据
th
th
from pyecharts.charts import Scatter
from pyecharts.commons.utils import JsCode
Py
c = (
Scatter() #创建散点图对象
Py
.add_xaxis(salary_data.salary_down.astype(int))#添加x周数据(薪资)
.add_yaxis(
"数据分析岗位数量", #y轴数据说明
[list(z) for z in zip(salary_data.job_count, salary_data.city)],#Y轴数据,岗位,城市
员
员
label_opts=opts.LabelOpts(#Js代码控制气泡显示提示文字
formatter=JsCode(
"function(params){return params.value[2]}" #提示
)
序
序
),
)
.set_global_opts(#全局变量
title_opts=opts.TitleOpts(title="数据分析就业岗位数量与平均起薪"),#设置标题
tooltip_opts=opts.TooltipOpts(#Js代码控制气泡弹窗提示文字
程
formatter=JsCode(
程
"function (params) {return params.value[2]+ '平均薪资:'+params.value[0]}"
)
),
visualmap_opts=opts.VisualMapOpts(#控制
马
马
type_="size", max_=1500, min_=200, dimension=1
),
xaxis_opts=opts.AxisOpts(min_=6000,name='平均起薪'),#设置X轴起始值,X轴名字
yaxis_opts=opts.AxisOpts(min_=300,max_=1550,name='岗位数量'),#设置Y轴起始值,Y轴名字
黑
)
)
c.render_notebook()
显示结果:
2.5 工作经验需求分析
on
on
工作经验数据清洗
job['experience'].value_counts()
显示结果:
th
th
1-3年 1197
1年经验 897
不限 854
Py
3-5年
2年经验
763
555
Py
经验3-5年 519
经验不限 483
3-4年经验 438
员
员
无需经验 387
经验1-3年 276
5-10年 206
经验5-10年 155
无经验 130
序
序
5-7年经验 103
1年以下 97
经验应届毕业生 95
10年以上 16
程
经验1年以下
8-9年经验
15
9
程
一年以下 8
经验10年以上 3
10年以上经验 2
马
马
Name: experience, dtype: int64
从上面结果中看出,工作经验的描述不尽相同,需要处理成统一格式
黑
job.experience.fillna('未知',inplace = True)
def process_experience(x):
if x in ['1-3年','2年经验','经验1-3年']:
return '1-3年'
elif x in ['3-5年','经验3-5年','3-4年经验']:
return '3-5年'
elif x in ['1年经验','1年以下','经验1年以下','一年以下','经验应届毕业生','不限','经验不限','无需经验','无经验']:
return '一年以下/应届生/经验不限'
elif x in ['5-10年','经验5-10年','5-7年经验','8-9年经验','10年以上经验','10年以上','经验10年以上']:
return '5年以上'
else:
return x
job['exp'] = job.experience.apply(process_experience)
job['exp'].value_counts()
显示结果:
一年以下/应届生/经验不限 2966
1-3年 2028
3-5年 1720
未知 718
5年以上 494
Name: exp, dtype: int64
圆环图展示结果
on
c.render_notebook()
显示结果:
th
th
Py
Py
员
员
序
序
程
程
小结
echarts是基于js的开源可视化库,pyecharts是echarts的python封装,利用pyecharts可以绘制具备交互性的炫酷图形
马
马
pyecharts 1.*版本的绘图api还是具有一定规律的
Pie(), Bar() .... 创建绘图对象
.add() /add_xaxis,add_yaxis添加数据
黑
.set_global_opts() 设置全局参数
render_notebook()在notebook中绘制、render()生成文件
更加详细的API可以参考pyecharts的官方文档和案例
1 案例介绍
案例背景
对APP下载和评分数据分析帮助App开发者获取和留存用户
通过对应用商店的数据分析为开发人员提供可操作的意见
通过数据分析要解决的问题
免费和收费的App都集中在哪些类别
收费app的价格是如何分布的,不同类别的价格分布怎样
App文件的大小和价格以及用户评分之间是否有关
分析流程
数据概况分析
数据行/列数量
缺失值分布
单变量分析
数字型变量的描述指标(平均值,最小值,最大值,标准差等)
类别型变量(多少个分类,各自占比)
多变量分析
按类别交叉对比
变量之间的相关性分析
可视化分析
分布趋势(直方图)
不同组差异(柱状图)
相关性(散点图/热力图)
数据字段说明
id : App ID 每个App唯一标识
track_name: App的名称
size_bytes: 以byte为单位的app大小
price:定价(美元)
rating_count_tot: App所有版本的用户评分数量
rating_count_ver: App当前版本的用户评分数量
prime_genre: App的类别
on
on
user_rating: App所有版本的用户评分
sup_devices.num: 支持的iOS设备数量
ipadSc_urls.num: app提供的截屏展示数量
lang.num 支持的语言数量
2 数据清洗
th
th
加载数据查看数据基本信息
Py
#调用基本包
import pandas as pd
Py
#数据读取
app=pd.read_csv('data/applestore.csv')
#数据的基本信息
员
员
app.info()
显示结果:
序
序
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7197 entries, 0 to 7196
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
程
马
5 rating_count_tot 7197 non-null int64
6 user_rating 7197 non-null float64
7 prime_genre 7197 non-null object
8 sup_devices 7197 non-null int64
黑
app.head()
显示结果:
Unnamed: 0 id track_name size_bytes price rating_count_tot user_rating prime_genre sup_devices ipadSc_urls lang
2 2 281940292 WeatherBug - Local Weather, Radar, Maps, Alerts 100524032 0.00 188583 3.5 Weather 37 5 3
3 3 282614216 eBay: Best App to Buy, Sell, Save! Online Shop... 128512000 0.00 262241 4.0 Shopping 37 5 9
发现了unname 0这个奇怪的变量,需要进行清理
app.drop('Unnamed: 0',axis=1,inplace=True)
#drop默认是对行
#inplace表示直接替换掉原有数据
#同样可以用位置来取
#app.drop(app.columns[0],axis=1,inplace=True)
app.describe()
显示结果:
id size_bytes price rating_count_tot user_rating sup_devices ipadSc_urls lang
考虑将sizebytes变成mb,新增数据
显示结果:
count 7197.000000
mean 189.909414
std 342.566408
min 0.562500
25% 44.749023
50% 92.652344
75% 173.497070
max 3839.463867
on
on
Name: size_mb, dtype: float64
根据价格新增标签
th
app.paid.describe()
显示结果:
Py
count
mean
7197.000000
0.436432
Py
std 0.495977
min 0.000000
25% 0.000000
员
员
50% 0.000000
75% 1.000000
max 1.000000
Name: paid, dtype: float64
序
序
小结
清洗异常值(unamed)
处理了给分析造成难度的值(size-bytes)
添加了方便分析的特征(免费/收费)
程
程
3 单变量分析
#value_counts (price,prime_genre)
马
马
#value_Coutn只能对应series,不能对整个dataframe做操作
app.price.value_counts()
显示结果:
黑
0.00 4056
0.99 728
2.99 683
1.99 621
4.99 394
3.99 277
6.99 166
9.99 81
5.99 52
7.99 33
14.99 21
19.99 13
8.99 9
24.99 8
13.99 6
11.99 6
29.99 6
12.99 5
15.99 4
59.99 3
17.99 3
22.99 2
23.99 2
20.99 2
27.99 2
16.99 2
49.99 2
39.99 2
74.99 1
18.99 1
34.99 1
99.99 1
299.99 1
47.99 1
21.99 1
249.99 1
Name: price, dtype: int64
从数据中可以看出,价格>50的比较少,将价格快速分组
bins = [0,2,10,300]
labels = ['<2', '<10','<300']
app['price_new']=pd.cut(app.price, bins, right=False, labels=labels)
#分组后查看数据分布情况
app.groupby(['price_new'])['price'].describe()
显示结果:
on
price_new
th
groupby的操作,不同类别app的价格分布
app.groupby(['prime_genre'])['price'].describe()
Py
显示结果:
Py
count mean std min 25% 50% 75% max
prime_genre
员
员
Book 112.0 1.790536 3.342210 0.0 0.0 0.00 2.99 27.99
序
Education 453.0 4.028234 18.725946 0.0 0.0 2.99 2.99 299.99
程
Food & Drink 63.0 1.552381 3.972119 0.0 0.0 0.00 1.49 27.99
Health & Fitness 180.0 1.916444 2.052378 0.0 0.0 1.99 2.99 9.99
马
马
Lifestyle 144.0 0.885417 1.478410 0.0 0.0 0.00 1.24 4.99
Photo & Video 349.0 1.473295 2.280703 0.0 0.0 0.99 1.99 22.99
Social Networking 167.0 0.339880 1.142210 0.0 0.0 0.00 0.00 9.99
删除价格大于等于49.99的app
app=app[app['price']<=49.99]
#评论情况分析
app.rating_count_tot.describe()
显示结果:
count 7.190000e+03
mean 1.290515e+04
std 7.577526e+04
min 0.000000e+00
25% 2.725000e+01
50% 3.005000e+02
75% 2.796750e+03
max 2.974676e+06
Name: rating_count_tot, dtype: float64
对用户打分的分组
bins = [0,1000,5000,100000,5000000]
app['rating_new']=pd.cut(app.rating_count_tot, bins, right=False)
#用户打分和价格的关系
app.groupby(['rating_new'])['price'].describe()
显示结果:
rating_new
[0, 1000) 4587.0 1.798696 3.324682 0.0 0.0 0.0 2.99 49.99
[1000, 5000) 1193.0 1.740721 3.203853 0.0 0.0 0.0 2.99 39.99
on
on
[5000, 100000) 1192.0 0.963549 1.984895 0.0 0.0 0.0 0.99 14.99
[100000, 5000000) 218.0 0.196376 0.925160 0.0 0.0 0.0 0.00 7.99
4 业务数据可视化
th
th
#可视化部分
import matplotlib.pyplot as plt
import seaborn as sns
Py
%matplotlib inline
#app评分关系
Py
plt.figure(figsize=(30,20))#调整大小
sns.relplot(x="prime_genre", y="user_rating",kind='line',
data=app) #折线图
员
员
显示结果:
<seaborn.axisgrid.FacetGrid at 0x1afb7996080>
序
序
<Figure size 2160x1440 with 0 Axes>
程
程
马
马
黑
从上图中可以看出,大部分评分集中在2分和4分之间,但是plt.figure(figsize=(30,20)) 并没有起作用
sns.relplot(x="prime_genre", y="user_rating",kind='line',data=app,height=5,aspect=3) \
#讲x轴文字旋转45度
plt.xticks(
rotation=45,
horizontalalignment='right',
fontweight='light',
fontsize='x-large'
)
显示结果:
价格分布
app1=app[app['price']<=9.99]
#直方图,APP价格的分布
sns.distplot(app1['price'])
显示结果:
on
on
th
th
Py
Py
员
员
从上面的结果中看出,大部分应用都是免费的,极少数APP的收费>5元
业务问题2:收费app的价格分布是如何的?不同类别之间有关系吗?
plt.figure(figsize=(15,8))#调整大小
序
序
sns.boxplot(x='price',y='prime_genre',data=app[app['paid']==1])
plt.yticks(fontweight='light',fontsize='x-large')
显示结果:
程
程
马
马
黑
价格绝大部分都集中在9.99美元以内,个别类别(如医疗)等因专业性总体价格会高于其他类别
通过箱线图,绘制前五个类别的app价格
#只保留应用数量最多的前5个类别
top5 = app.groupby(['prime_genre'])['price'].count().sort_values(ascending = False).head().index.tolist()
app5 = app[app.prime_genre.isin(top5)]
plt.figure(figsize=(10,8))#调整大小
sns.boxplot(x='price',y='prime_genre',data=app5[app['paid']==1])
显示结果:
on
on
从上图可以看出,Games的价格分布更广,最大值也较高,异常值也较多
关于箱线图
image-20191130033201482
箱子的中间有一条线,代表了数据的中位数
th
th
箱子的上下底,分别是数据的上四分位数(Q3)和下四分位数(Q1)
箱体包含了50%的数据。因此,箱子的高度在一定程度上反映了数据的波动程度
上下边缘则代表了该组数据的最大值和最小值
有时候箱子外部会有一些点,可以理解为数据中的“异常值”
Py
散点图,价格和用户评分的分布
Py
plt.figure(figsize=(10,8))
sns.scatterplot(x='price',y='user_rating',data=app)
员
员
显示结果:
序
序
程
程
马
马
黑
从散点图可以看出,价格和评价关联不强,高价的应用评价两级分化,但数据相对较少
柱状图,前5个类别app的用户评分均值
#同一类别,将免费和付费的评分进行对比
plt.figure(figsize=(10,8))
sns.barplot(x='prime_genre',y='user_rating',hue='paid',data=app5)
显示结果:
on
on
5 业务解读
问题一 免费或收费APP集中在哪些类别
第一步,将数据加总成每个类别有多少个app
th
th
第二步,从高到低进行排列
第三步,将数据进行可视化
#使用countplot--count是对数据加总,plot将数据进行可视化
Py
#参数order 指定数据显示的顺序
plt.figure(figsize=(20,10))
Py
sns.countplot(y='prime_genre',hue='paid',data=app,order=app['prime_genre'].value_counts().index)
plt.tick_params(labelsize=20)
员
员
显示结果:
序
序
程
程
马
马
黑
业务解答:免费或收费都是高度集中在游戏类别
免费与收费的APP在不同评分区间的分布
将评分进行分箱,查看落入不同箱中应用的数量
bins=[0,0.5,2.5,4.5,5.1]
app['rating_level']=pd.cut(app.user_rating,bins,right=False)
app.groupby(['rating_level'])['user_rating'].describe()
显示结果:
rating_level
[0.0, 0.5) 929.0 0.000000 0.000000 0.0 0.0 0.0 0.0 0.0
[0.5, 2.5) 206.0 1.650485 0.400213 1.0 1.5 2.0 2.0 2.0
[2.5, 4.5) 2903.0 3.646056 0.467987 2.5 3.5 4.0 4.0 4.0
[4.5, 5.1) 3152.0 4.578046 0.181500 4.5 4.5 4.5 4.5 5.0
plt.figure(figsize=(15,8))
sns.countplot(x='paid',hue='rating_level',data=app)
显示结果:
免费和收费APP,评分的分布基本相似,收费的APP低分的相对少一些
on
on
业务问题3:APP的大小和用户评分之间有关系吗?
通过热力图来查看变量之间两两相关系数
q4=['user_rating','price','size_mb']
th
th
app[q4].corr()
显示结果:
user_rating 1.000000
Py
0.073237 0.066160
员
size_mb 0.066160 0.314386 1.000000
sns.heatmap(app[q4].corr())
#热力图,展现变量之间两两之间关系的强弱
序
序
显示结果:
程
程
马
马
黑
业务解答:应用的大小、价格与评分没有很明显的关联,但是价格和大小之间有正相关关系
小结
常规的探索性数据分析套路:查看概况->单变量分析->多变量分析->可视化分析
Seaborn绘图的时候,有些api在调整图片大小时,使用plt.figure(figsize=())无效,此时可以使用 height 关键字来控制图片高度
aspect 控制宽高比例
Seaborn在绘制柱状图的时候,可以使用hue参数 传入类别型变量,方便进行对比
19_Uniqlo销售数据分析案例
学习目标
掌握描述性数据分析流程
熟练使用pandas、seaborn进行数据分析和可视化
1 案例介绍
案例背景
数据集中包含了不同城市优衣库门店的销售记录
通过对销售数据的分析,为运营提供一些有益信息
通过数据分析要解决的问题
不同产品的销售情况,顾客喜欢的购买方式
销售额和成本之间的关系
购买时间偏好
数据字段说明
Store_id 门店随机id
City 城市
Channel 销售渠道 网购自提 门店购买
gender_group 客户性别 男女
age_group 客户年龄段
wkd_ind 购买发生的时间(周末,周间)
Product 产品类别
customer 客户数量
revenue 销售金额
Order 订单数量
Quant 购买产品的数量
unit_cost 成本(制作+运营)
on
on
2 加载数据
import pandas as pd
uniqlo = pd.read_csv('data/uniqlo.csv')
th
th
uniqlo.head()
显示结果:
Py
1
store_id
658
146
city
深圳
杭州
channel
线下
线下
gender_group
Female
Female
age_group
25-29
25-29
Pywkd_ind
Weekday
Weekday
product
当季新品
运动
customer
1
revenue
796.0
149.0
order
1
quant
1
unit_cost
59
49
员
uniqlo.info()
显示结果:
序
序
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 22293 entries, 0 to 22292
Data columns (total 12 columns):
程
store_id
city
22293 non-null int64
22293 non-null object
程
channel 22293 non-null object
gender_group 22293 non-null object
age_group 22293 non-null object
马
马
wkd_ind 22293 non-null object
product 22293 non-null object
customer 22293 non-null int64
revenue 22293 non-null float64
order 22293 non-null int64
黑
从上面结果中看出,数据没有缺失
uniqlo.describe()
显示结果:
显示结果:
store_id city channel gender_group age_group wkd_ind product customer revenue order quant unit_cost
on
17259 336 西安 线下 Female 20-24 Weekday 配件 1 0.00 1 1 29
th
20180 336 西安 线下 Male 20-24 Weekday T恤 1 0.00 1 1 49
进一步查看之后发现,一部分订单收入为0,可能为赠品,收入小于0的订单只有一个,实际工作中可以跟业务方核对数据
Py
查看金额较大的订单
Py
uniqlo[uniqlo.revenue>5000]
显示结果:
员
员
store_id city channel gender_group age_group wkd_ind product customer revenue order quant unit_cost
序
9628 19 南京 线下 Female 45-49 Weekday 当季新品 42 12538.00 45 70 59
成都 线下 当季新品
查看后发现均为消费多件导致金额较高,数据没有问题
马
马
3 不同产品的销售情况
黑
uniqlo.groupby('product')['store_id'].count().sort_values(ascending = False)
显示结果:
product
T恤 10610
当季新品 2540
袜子 2053
短裤 1694
配件 1572
牛仔裤 1412
运动 976
毛衣 807
裙子 629
Name: store_id, dtype: int64
可以看出,T恤销售记录条数最多,其次是当季新品,袜子...
显示结果:
quant
product
T恤 18425
当季新品 5338
配件 4622
袜子 3639
短裤 2821
牛仔裤 2432
运动 1794
毛衣 1356
裙子 995
从上面结果中看出销售件数的排行
进一步拆解,按城市拆解销量
on
显示结果:
city 上海 北京 南京 广州 成都 杭州 武汉 深圳 西安 重庆
product
th
th
T恤 2118 800 568 1681 1079 3100 2964 3697 1145 1273
当季新品 550 188 266 459 329 840 862 1126 281 437
Py
配件
袜子
550
370
138
123
126
117
406
379
Py 225
240
777
662
755
568
1046
735
260
186
339
259
员
运动 171 31 17 174 125 351 282 364 118 161
序
对城市拆解后,再进一步按线上线下拆解
程
显示结果:
city 上海 北京 南京 广州 成都 杭州 武汉 深圳 西安 重庆
马
马
channel 线上 线下 线下 线下 线上 线下 线下 线下 线上 线下 线下 线上 线下 线上 线下
product
T恤 591 1527 800 568 1087 594 1079 3100 1366 1598 3697 120 1025 90 1183
当季新品 177 373 188 266 273 186 329 840 328 534 1126 38 243 50 387
牛仔裤 91 216 46 52 119 75 178 388 162 253 477 27 125 16 207
短裤 116 190 87 49 172 94 179 456 267 223 608 28 146 43 163
袜子 121 249 123 117 241 138 240 662 308 260 735 40 146 26 233
配件 194 356 138 126 259 147 225 777 530 225 1046 51 209 20 319
4 用户习惯使用哪种方式进行消费
uniqlo.channel.value_counts()
显示结果:
线下 18403
线上 3890
Name: channel, dtype: int64
从数据中看出,线下消费记录更多,线上较少,进一步按城市拆解
plt.figure(figsize=(15,8))
sns.countplot(y='city',hue='channel',data=uniqlo)
显示结果:
数据中发现,并不是所有的城市都有线下,有些城市线上的数据条目数比线下多,比如广州
on
on
进一步计算线上线下销售额
uniqlo.pivot_table(index = 'city',columns='channel',values='quant',aggfunc='sum')
显示结果:
th
th
channel 线上 线下
city
Py
上海 1393.0
Py 3212.0
北京 NaN 1477.0
南京 NaN 1265.0
员
员
广州 2387.0 1428.0
成都 NaN 2524.0
杭州 NaN 6975.0
序
序
武汉 3241.0 3462.0
深圳 NaN 8502.0
程
西安 351.0 2141.0
程
重庆 280.0 2784.0
马
5 用户消费习惯(周间还是周末)
查看整体情况
黑
uniqlo.wkd_ind.value_counts()
显示结果:
Weekday 12465
Weekend 9828
Name: wkd_ind, dtype: int64
整体情况上看,周间数据条目数量>周末数量,但周间有5天,所以整体看
通过数据透视表,查看不同城市,周间周末销量情况
显示结果:
city 上海 北京 南京 广州 成都 杭州 武汉 深圳 西安 重庆
wkd_ind
Weekday 2637 869 818 2318 1552 4169 4013 4993 1399 1778
Weekend 1968 608 447 1497 972 2806 2690 3509 1093 1286
添加字段,计算每天的销售额
wkd_sales.loc['weekday_avg',:] = wkd_sales.loc['Weekday',:]/5
wkd_sales.loc['weekend_avg',:] = wkd_sales.loc['Weekend',:]/2
wkd_sales
显示结果:
city 上海 北京 南京 广州 成都 杭州 武汉 深圳 西安 重庆
wkd_ind
Weekday 2637.0 869.0 818.0 2318.0 1552.0 4169.0 4013.0 4993.0 1399.0 1778.0
Weekend 1968.0 608.0 447.0 1497.0 972.0 2806.0 2690.0 3509.0 1093.0 1286.0
weekday_avg 527.4 173.8 163.6 463.6 310.4 833.8 802.6 998.6 279.8 355.6
weekend_avg 984.0 304.0 223.5 748.5 486.0 1403.0 1345.0 1754.5 546.5 643.0
可以看出每个城市周间周末平均消费订单数量的情况
6 销售额和成本之间的关系
销售额 revenue 和 unit_cost 成本之间的关系,先直接计算皮尔逊相关系数
uniqlo[['revenue','unit_cost']].corr()
on
on
显示结果:
revenue unit_cost
th
unit_cost 0.14844 1.00000
从结果中查看发现,貌似没有关系,但进一步查看unit_cost
Py
uniqlo.unit_cost.value_counts()
Py
显示结果:
49 11586
员
员
59 3169
9 2053
19 1694
29 1572
序
序
69 1412
99 807
Name: unit_cost, dtype: int64
查看发现unit_cost为单位商品的成本,但是revenue不一定是一件物品的收入,需要处理数据
程
程
uniqlo2 = uniqlo[uniqlo.revenue>1]
uniqlo2.head()
马
马
显示结果:
store_id city channel gender_group age_group wkd_ind product customer revenue order quant unit_cost rev_per_goods
#添加单件收入列,使用单件收入和单位成本计算相似度
uniqlo2.loc[:,'rev_per_goods'] = uniqlo2['revenue']/uniqlo2['quant']
uniqlo2[['rev_per_goods','unit_cost']].corr()
显示结果:
rev_per_goods unit_cost
从结果中看出,单件收入和单位成本之间相关性还是比较强的,绘制热力图
sns.heatmap(uniqlo2[['rev_per_goods','unit_cost']].corr())
显示结果:
小结
数据分析的过程中,多维度拆解是很重要的分析思路,在分析产品销售情况的时候,案例中分别按城市,按线上线下进行拆解,实际
上还可以从更多的维度进行拆解,比如年龄,性别等均可以拆解,拆解之后可以获得更多信息
使用seaborn进行可视化代码比较简洁,有些api还自带了简单的统计功能,比如countplot
透视表(pivot_table)和分组聚合(groupby aggragation)功能类似,都可以从不同角度来观察数据,可以互相替换,当分组字段有多个
时,groupby和unstack配合使用,结果与透视表一样,举例如下
计算不同城市线上线下销量情况
on
on
uniqco.pivot_table(index = 'city',columns='channel',values='quant',aggfunc='sum')
显示结果:
channel 线上 线下
th
th
city
上海 1393.0 3212.0
Py
北京 NaN
Py 1477.0
南京 NaN 1265.0
广州 2387.0 1428.0
员
员
成都 NaN 2524.0
杭州 NaN 6975.0
武汉 3241.0 3462.0
序
序
深圳 NaN 8502.0
西安 351.0 2141.0
程
重庆 280.0 2784.0
程
#使用groupby
uniqco.groupby(['city','channel'])['quant'].agg('sum')
马
马
显示结果:
city channel
黑
上海 线上 1393
线下 3212
北京 线下 1477
南京 线下 1265
广州 线上 2387
线下 1428
成都 线下 2524
杭州 线下 6975
武汉 线上 3241
线下 3462
深圳 线下 8502
西安 线上 351
线下 2141
重庆 线上 280
线下 2784
Name: quant, dtype: int64
分组之后的结果,与透视表有差别,在上面结果基础上调用unstack(),就可以得到与透视表一样的结果
uniqco.groupby(['city','channel'])['quant'].agg('sum').unstack()
显示结果:
channel 线上 线下
city
上海 1393.0 3212.0
北京 NaN 1477.0
南京 NaN 1265.0
广州 2387.0 1428.0
成都 NaN 2524.0
杭州 NaN 6975.0
武汉 3241.0 3462.0
深圳 NaN 8502.0
西安 351.0 2141.0
重庆 280.0 2784.0
20_RFM会员价值度模型案例
on
on
学习目标
知道RFM模型的概念和使用方法
掌握如何使用Python进行RFM分群
th
th
知道如何使用Pyecharts绘制3D图形
1 会员价制度模型介绍
Py
Py
会员价值度用来评估用户的价值情况,是区分会员价值的重要模型和参考依据,也是衡量不同营销效果的关键指标之一。
价值度模型一般基于交易行为产生,衡量的是有实体转化价值的行为。常用的价值度模型是RFM
RFM模型是根据会员
最近一次购买时间R(Recency)
员
员
购买频率F(Frequency)
购买金额M(Monetary)计算得出RFM得分
通过这3个维度来评估客户的订单活跃价值,常用来做客户分群或价值区分
RFM模型基于一个固定时间点来做模型分析,不同时间计算的的RFM结果可能不一样
序
序
R F M 用户类别
高 高 高 重要价值用户
程
高 低 高 重要发展用户
程
低 高 高 重要保持用户
低 低 高 重要挽留用户
马
马
高 高 低 一般价值用户
高 低 低 一般发展用户
黑
低 高 低 一般保持用户
低 低 低 一般挽留用户
RFM模型的基本实现过程:
①设置要做计算时的截止时间节点(例如2017-5-30),用来做基于该时间的数据选取和计算。
②在会员数据库中,以今天为时间界限向前推固定周期(例如1年),得到包含每个会员的会员ID、订单时间、订单金额的原始数
据集。一个会员可能会产生多条订单记录。
③ 数据预计算。从订单时间中找到各个会员距离截止时间节点最近的订单时间作为最近购买时间;以会员ID为维度统计每个用户
的订单数量作为购买频率;将用户多个订单的订单金额求和得到总订单金额。由此得到R、F、M三个原始数据量。
④ R、F、M分区。对于F和M变量来讲,值越大代表购买频率越高、订单金额越高;但对R来讲,值越小代表离截止时间节点越
近,因此值越好。对R、F、M分别使用五分位(三分位也可以,分位数越多划分得越详细)法做数据分区。需要注意的是,对于
R来讲需要倒过来划分,离截止时间越近的值划分越大。这样就得到每个用户的R、F、M三个变量的分位数值。
⑤ 将3个值组合或相加得到总的RFM得分。对于RFM总得分的计算有两种方式,一种是直接将3个值拼接到一起,例如RFM得分为
312、333、132;另一种是直接将3个值相加求得一个新的汇总值,例如RFM得分为6、9、6。
Excel实现RFM划分案例
以某电商公司为例
R:例如:正常新用户注册1周内交易,7天是重要的值,日用品采购周期是1个月,30天是重要的值
F:例如:1次购买,2次购买,3次购买,4~10次,10次以上
M:例如:客单价300,热销单品价格240 等
常见的确定RFM划分区间的套路
业务实际判断
平均值或中位数
二八法则
提取用户最近一次的交易时间,算出距离计算时间的差值
获取当前时间=TODAY()
on 计算时间间隔
on
根据天数长短赋予对应的R值
=IF(D2>60,1,IF(D2>30,2,IF(D2>14,3,IF(D2>7,4,5))))
th
th
Py
Py
从历史数据中取出所有用户的购买次数,根据次数多少赋予对应的F分值
=IF(E2>10,5,IF(E2>3,4,IF(E2>2,3,IF(E2>1,2,1))))
员
员
序
序
程
从历史数据中汇总,求得该用户的交易总额,根据金额大小赋予对应的M值
程
=IF(F2>1000,5,IF(F2>500,4,IF(F2>300,3,IF(F2>230,2,1))))
马
马
黑
求出RFM的中值,例如中位数,用中值和用户的实际三值进行比较,高于中值的为高,否则为低
在得到不同会员的RFM之后,根据步骤⑤产生的两种结果有两种应用思路
思路1:基于3个维度值做用户群体划分和解读,对用户的价值度做分析
得分为212的会员往往购买频率较低,针对购买频率低的客户应定期发送促销活动邮件
得分为321的会员虽然购买频率高但是订单金额低等,这些客户往往具有较高的购买黏性,可以考虑通过关联或搭配销售的
方式提升订单金额。
思路2:基于RFM的汇总得分评估所有会员的价值度价值,并可以做价值度排名。同时,该得分还可以作为输入维度与其他维度一
起作为其他数据分析和挖掘模型的输入变量,为分析建模提供基础。
2 RFM计算案例
2.1 案例背景
用户价值细分是了解用户价值度的重要途径,针对交易数据分析的常用模型是RFM模型
业务对RFM的结果要求
对用户做分组
将每个组的用户特征概括和总结出来,便于后续精细化运营不同的客户群体,且根据不同群体做定制化或差异性的营销和关怀
规划目标将RFM的3个维度分别做3个区间的离散化
用户群体最大有3×3×3=27个
划分区间过多则不利于用户群体的拆分
区间过少则可能导致每个特征上的用户区分不显著
交付结果
给业务部门做运营的分析结果要导出为Excel文件,用于做后续分析和二次加工使用
RFM的结果还会供其他模型的建模使用,RFM本身的结果可以作为新的局部性特征,因此数据的输出需要有本地文件和写数据库
两种方式
数据说明
选择近4年订单数据,从不同的年份对比不同时间下各个分组的绝对值变化情况,方便了解会员的波动
案例的输入源数据sales.xlsx
程序输出RFM得分数据写入本地文件sales_rfm_score.xlsx和MySQL数据库sales_rfm_score表中
2.2 用到的技术点
通过Python代码手动实现RFM模型,主要用到的库包括
time、numpy和pandas
在结果展示时使用了pyecharts的3D柱形图
on
on
2.3 案例数据
案例数据是某企业从2015年到2018年共4年的用户订单抽样数据,数据来源于销售系统
数据在Excel中包含5个sheet,前4个sheet以年份为单位存储为单个sheet中,最后一张会员等级表为用户的等级表
th
th
前4张表的数据概要如下。
特征变量数:4
数据记录数:30774/41278/50839/81349
Py
是否有NA值:有
是否有异常值:有
Py
·会员ID:每个会员的ID唯一,由纯数字组成。 ·提交日期:订单日提交日期。 ·订单号:订单ID,每个订单的ID唯一,由纯数字组
成。 ·订单金额:订单金额,浮点型数据。
员
员
具体数据特征如下:
会员ID:每个会员的ID唯一,整型
提交日期:订单日提交日期
订单号:订单ID,每个订单的ID唯一,整型
序
序
订单金额:订单金额,浮点型数据
会员登记表中是所有会员的会员ID对应会员等级的情况,包括以下两个字段
会员ID:该ID可与前面的订单表中的会员ID关联
会员等级:会员等级以数字区分,数字越大,级别越高
程
程
2.4 代码
导入模块
马
马
import time # 时间库
用到了6个库:time、numpy、pandas、pymysql、sklearn和pyecharts。
time:用来记录插入数据库时的当前日期
numpy:用来做基本数据处理等
pandas:有关日期转换、数据格式化处理、主要RFM计算过程等
pymysql:数据库连接工具,读写MySQL数据库。
sklearn:使用其中的随机森林库
pyecharts:展示3D柱形图
读取数据
sheet_names = ['2015','2016','2017','2018','会员等级']
sheet_datas = [pd.read_excel('data/sales.xlsx',sheet_name=i) for i in sheet_names]
查看数据基本情况
for each_name,each_data in zip(sheet_names,sheet_datas):
print('[data summary for ============={}===============]'.format(each_name))
print('Overview:','\n',each_data.head(4))# 展示数据前4条
print('DESC:','\n',each_data.describe())# 数据描述性信息
print('NA records',each_data.isnull().any(axis=1).sum()) # 缺失值记录数
print('Dtypes',each_data.dtypes) # 数据类型
输出结果:
on
max 3.954613e+10 4.282025e+09 111750.000000
NA records 0
Dtypes 会员ID int64
订单号 int64
提交日期 datetime64[ns]
订单金额 float64
th
th
dtype: object
[data summary for =============2016===============]
Overview:
会员ID 订单号 提交日期 订单金额
Py
0
1
2
39288120141
39293812118
27596340905
4282025766 2016-01-01
4282037929 2016-01-01
4282038740 2016-01-01
Py
76.0
7599.0
802.0
3 15111475509 4282043819 2016-01-01 65.0
DESC:
会员ID 订单号 订单金额
员
员
count 4.127800e+04 4.127800e+04 41277.000000
mean 2.908415e+10 4.313583e+09 957.106694
std 1.389468e+10 1.094572e+07 2478.560036
min 8.100000e+01 4.282026e+09 0.100000
25% 1.934990e+10 4.309457e+09 59.000000
序
序
50% 3.730339e+10 4.317545e+09 147.000000
75% 3.923182e+10 4.321132e+09 888.000000
max 3.954554e+10 4.324911e+09 174900.000000
NA records 1
程
Dtypes 会员ID
订单号 int64
int64
程
提交日期 datetime64[ns]
订单金额 float64
dtype: object
马
马
[data summary for =============2017===============]
Overview:
会员ID 订单号 提交日期 订单金额
0 38765290840 4324911135 2017-01-01 1799.0
1 39305832102 4324911213 2017-01-01 369.0
黑
on
mean 2.980055e+10 2.259701
std 1.365654e+10 1.346408
min 8.100000e+01 1.000000
25% 2.213894e+10 1.000000
50% 3.833022e+10 2.000000
75% 3.927932e+10 3.000000
th
th
max 3.954614e+10 5.000000
NA records 0
Dtypes 会员ID int64
会员等级 int64
dtype: object
Py
结果说明
Py
每个sheet中的数据都能正常读取,无任何错误
日期列(提交日期)已经被自动识别为日期格式,后期不必转换
员
员
订单金额的分布是不均匀的,里面有明显的极大值
例如2016年的数据中,最大值为174900,最小值仅为0.1
极大极小值相差过大,数据会受极值影响
订单金额中的最小值包括0、0.1这样的金额,可能为非正常订单,与业务方沟通后确认
序
序
最大值的订单金额有效,通常是客户一次性购买多个大家电商品
而订单金额为0.1元这类使用优惠券支付的订单,没有实际意义
除此之外,所有低于1元的订单均有这个问题,因此需要在后续处理中去掉
有的表中存在缺失值记录,但数量不多,选择丢弃或填充均可
程
程
数据预处理
# 去除缺失值和异常值
马
马
for ind,each_data in enumerate(sheet_datas[:-1]):
sheet_datas[ind] = each_data.dropna()# 丢弃缺失值记录
sheet_datas[ind] = each_data[each_data['订单金额'] > 1]# 丢弃订单金额<=1的记录
sheet_datas[ind]['max_year_date'] = each_data['提交日期'].max() # 增加一列最大日期值
黑
通过for循环配合enumerate方法,获得每个可迭代元素的索引和具体值
处理缺失值和异常值只针对订单数据,因此sheet_datas通过索引实现不包含最后一个对象(即会员等级表)
直接将each_data使用dropna丢弃缺失值后的dataframe代原来sheet_datas中的dataframe
使用each_data[each_data['订单金额']>1]来过滤出包含订单金额>1的记录数,然后替换原来sheet_datas中的dataframe
最后一行代码的目的是在每个年份的数据中新增一列max_year_date,通过each_data['提交日期'].max()获取一年中日期的最大
值,这样方便后续针对每年的数据分别做RFM计算,而不是针对4年的数据统一做RFM计算。
# 汇总所有数据
data_merge = pd.concat(sheet_datas[:-1],axis=0)
# 获取各自年份数据
data_merge['date_interval'] = data_merge['max_year_date']-data_merge['提交日期']
data_merge['year'] = data_merge['提交日期'].dt.year
# 转换日期间隔为数字
data_merge['date_interval'] = data_merge['date_interval'].apply(lambda x: x.days)
data_merge.head()
输出结果:
会员ID 订单号 提交日期 订单金额 max_year_date date_interval year
获取各自年份数据:
先计算各自年份的最大日期与每个行的日期的差,得到日期间隔
再增加一列新的字段,为每个记录行发生的年份,使用data_merge['提交日期'].dt.year实现
关于pandas的 datetime类型
dt是pandas中Series时间序列datetime类属性的访问对象
除了代码中用到的year外,还包括:date、dayofweek、dayofyear、days_in_month、freq、days、hour、
microsecond、minute、month、quarter、second、time、week、weekday、weekday_name、weekofyear等
转换日期间隔为数字:data_merge['date_interval'].apply(lambda x: x.days) 是将data_merge['date_interval']的时间间隔转换
为数值型计算对象,这里使用了apply方法。Apply方法是对某个Pandas对象(dataframe或Series)使用自定义函数
# 按会员ID做汇总
rfm_gb = data_merge.groupby(['year','会员ID'],as_index=False).agg(
{'date_interval': 'min', # 计算最近一次订单时间
on
on
'提交日期': 'count', # 计算订单频率
'订单金额': 'sum'}) # 计算订单总金额
# 重命名列名
rfm_gb.columns = ['year','会员ID','r','f','m']
rfm_gb.head()
th
th
输出结果:
year 会员ID r f m
Py
0 2015
Py
267 197 2 105.0
员
3 2015 343 300 1 118.0
序
上面代码框中的第一行代码,是基于年份和会员ID,分别做RFM原始值的聚合计算
这里使用groupby分组,以year和会员ID为联合主键,设置as_index=False意味着year和会员ID不作为index列,而是普通的数据
框结果列。后面的agg方法实际上是一个“批量”聚合功能的函数,它实现了对date_interval、提交日期、订单金额三列分别以
程
min、count、sum做聚合计算的功能。否则,我们需要分别写3条goupby来实现3个聚合计算
程
确定RFM划分区间 在做RFM划分时,基本逻辑是分别对R、F、M做离散化操作,然后再计算RFM。而离散化本身有多种方法可选,由
于我们要对数据做RFM离散化,因此需要先看下数据的基本分布状态
马
马
# 查看数据分布
desc_pd = rfm_gb.iloc[:,2:].describe().T
desc_pd
黑
输出结果:
# 定义区间边界
r_bins = [-1,79,255,365] # 注意起始边界小于最小值
f_bins = [0,2,5,130]
m_bins = [0,69,1199,206252]
从基本概要看出
汇总后的数据总共有14万条
r和m的数据分布相对较为离散,表现在min、25%、50%、75%和max的数据没有特别集中
而从f(购买频率)则可以看出,大部分用户的分布都趋近于1,表现是从min到75%的分段值都是1且mean(均值)才为
1.365
计划选择25%和75%作为区间划分的2个边界值
f的分布情况说明
r和m本身能较好地区分用户特征,而f则无法区分(大量的用户只有1个订单)
行业属性(家电)原因,1年购买1次比较普遍(其中包含新客户以及老客户在当年的第1次购买)
与业务部门沟通,划分时可以使用2和5来作为边界
业务部门认为当年购买>=2次可被定义为复购用户(而非累计订单的数量计算复购用户)
业务部门认为普通用户购买5次已经是非常高的次数,超过该次数就属于非常高价值用户群体
该值是基于业务经验和日常数据报表获得的
区间边界的基本原则如下
中间2个边界值:r和m是分别通过25%和75%的值获取的,f是业务与数据部门定义的。
最小值边界:比各个维度的最小值小即可。
最大值边界:大于等于各个维度的最大值即可
最小值边界为什么要小于各个维度的最小值:
这是由于在边界上的数据归属有一个基本准则,要么属于区间左侧,要么属于区间右侧。如,f_bins中的2处于边界
上,要么属于左侧区间,要么属于右侧区间
在后续使用pd.cut方法中,对于自定义边界实行的是左开右闭的原则,即数据属于右侧区间,f_bins中的2就属于右侧
区间。最左侧的值是无法划分为任何区间的,因此,在定义最小值时,一定要将最小值的边界值
举例:[1,2,3,4,5],假如数据划分的区间边界是[1,3,5],即划分为2份
其中的2/3被划分到(1,3]区间中
3/4/5被划分到(3,5]区间中
1无法划分到任何一个正常区间内
RFM计算过程
# RFM分箱得分
# 计算R得
on
on
rfm_gb['r_score'] = pd.cut(rfm_gb['r'], r_bins, labels=[i for i in range(len(r_bins)-1,0,-1)])
分
rfm_gb['f_score'] = pd.cut(rfm_gb['f'], f_bins, labels=[i+1 for i in range(len(f_bins)-1)]) # 计算F得分
rfm_gb['m_score'] = pd.cut(rfm_gb['m'], m_bins, labels=[i+1 for i in range(len(m_bins)-1)]) # 计算M得分
每个rfm的过程使用了pd.cut方法,基于自定义的边界区间做划分
th
th
labels用来显示每个离散化后的具体值。F和M的规则是值越大,等级越高
而R的规则是值越小,等级越高,因此labels的规则与F和M相反
在labels指定时需要注意,4个区间的结果是划分为3份
Py
#计算RFM组合
Py
rfm_gb['r_score'] = rfm_gb['r_score'].astype(np.str)
rfm_gb['f_score'] = rfm_gb['f_score'].astype(np.str)
rfm_gb['m_score'] = rfm_gb['m_score'].astype(np.str)
rfm_gb['rfm_group'] = rfm_gb['r_score'].str.cat(rfm_gb['f_score']).str.cat(
员
员
rfm_gb['m_score'])
将3列作为字符串组合为新的分组
代码中,先针对3列使用astype方法将数值型转换为字符串型
序
序
然后使用pandas的字符串处理库str中的cat方法做字符串合并,该方法可以将右侧的数据合并到左侧
再连续使用两个str.cat方法得到总的R、F、M字符串组合
Series.str.cat(others=None, sep=None, na_rep=None) 参数: others : 列表或复合列表,默认为None,如果为None则连接本身的
元素 sep : 字符串 或者None,默认为None na_rep : 字符串或者 None, 默认 None。如果为None缺失值将被忽略。 返回值:
concat : 序列(Series)/索引(Index)/字符串(str)
程
程
#如果连接的是两个序列,则会对应
>>> pd.Series(['a', 'b', 'c']).str.cat(['A', 'B', 'C'], sep=',')
0 a,A
马
马
1 b,B
2 c,C
dtype: object
#否则则会连接自身序列里的值
>>> pd.Series(['a', 'b', 'c']).str.cat(sep=',')
黑
'a,b,c'
#也可以同时连接复合列表
>>> pd.Series(['a', 'b']).str.cat([['x', 'y'], ['1', '2']], sep=',')
0 a,x,1
1 b,y,2
dtype: object
保存RFM结果到Excel
rfm_gb.to_excel('sales_rfm_score1.xlsx') # 保存数据为Excel
写数据到数据库
# 数据库信息
config = {'host': '127.0.0.1', # 默认127.0.0.1
'user': 'root', # 用户名
'password': 'MyNewPass', # 密码
'port': 3306, # 端口,默认为3306
'database': 'test', # 数据库名称
'charset': 'utf8' # 字符编码
}
# 建表操作
con = pymysql.connect(**config) # 建立mysql连接
cursor = con.cursor() # 获得游标
cursor.execute("show tables") # 查询表
table_list = [t[0] for t in cursor.fetchall()] # 读出所有库
# 查找数据库是否存在目标表,如果没有则新建
table_name = 'sales_rfm_score' # 要写库的表名
if not table_name in table_list: # 如果目标表没有创建
cursor.execute('''
CREATE TABLE %s (
userid VARCHAR(20),
r_score int(2),
f_score int(2),
m_score int(2),
rfm_group VARCHAR(10),
insert_date VARCHAR(20)
)ENGINE=InnoDB DEFAULT CHARSET=utf8
''' % table_name) # 创建新表
# 梳理数据
write_db_data = rfm_gb[['会员ID','r_score','f_score','m_score','rfm_group']] # 主要数据
timestamp = time.strftime('%Y-%m-%d', time.localtime(time.time())) # 日期
# 写库
for each_value in write_db_data.values:
insert_sql = "INSERT INTO `%s` VALUES ('%s',%s,%s,%s,'%s','%s')" % \
on
on
(table_name, each_value[0], each_value[1], each_value[2], \
each_value[3],each_value[4],
timestamp) # 写库SQL依据
cursor.execute(insert_sql) # 执行SQL语句,execute函数里面要用双引号
con.commit() # 提交命令
cursor.close() # 关闭游标
th
th
con.close() # 关闭数据库连接
RFM图形展示
为了更好地了解不同周期下RFM分组人数的变化,通过3D柱形图展示结果
Py
展示结果时只有3个维度,分别是年份、rfm分组和用户数量。
Py
# 图形数据汇总
display_data = rfm_gb.groupby(['rfm_group','year'],as_index=False)['会员ID'].count()
员
员
display_data.columns = ['rfm_group','year','number']
display_data['rfm_group'] = display_data['rfm_group'].astype(np.int32)
display_data.head()
输出结果:
序
序
rfm_group year number
1 111 2016
程 1498
马
4 112 2015 3811
第1行代码使用数据框的groupby以rfm_group和year为联合对象,以会员ID会为计算维度做计数,得到每个RFM分组、年份下的
黑
会员数量
第2行代码对结果列重命名
第3行代码将rfm分组列转换为int32形式
# 显示图形
from pyecharts.commons.utils import JsCode
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
'#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
range_max = int(display_data['number'].max())
c = (
Bar3D()#设置了一个3D柱形图对象
.add(
"",#标题
[d.tolist() for d in display_data.values],#数据
xaxis3d_opts=opts.Axis3DOpts( type_="category",name='分组名称'),#x轴数据类型,名称
yaxis3d_opts=opts.Axis3DOpts( type_="category",name='年份'),#y轴数据类型,名称
zaxis3d_opts=opts.Axis3DOpts(type_="value",name='会员数量'),#z轴数据类型,名称
)
.set_global_opts(#设置颜色,及不同取值对应的颜色
visualmap_opts=opts.VisualMapOpts(max_=range_max,range_color=range_color),
title_opts=opts.TitleOpts(title="RFM分组结果"),#设置标题
)
)
c.render_notebook() #在notebook中显示
输出结果:
on
on
输出3D图像中
X轴为RFM分组、Y轴为年份、Z轴为用户数量
该3D图可旋转、缩放,以便查看不同细节
调节左侧的滑块条,用来显示或不显示特定数量的分组结果
th
th
2.5 案例结论
基于图形的交互式分析
重点人群分布:
Py
在整个分组中,212群体的用户是相对集中且变化最大的
Py
从2016年到2017年用户群体数量变化不大,但到2018年增长了近一倍
这部分人群将作为重点分析人群
重点分组分布:
员
员
除了212人群外,312、213、211及112人群都在各个年份占据很大数量
虽然各自规模不大,但组合起来的总量超过212本身,也要重点做分析。
如果拖动左侧的滑块,仅过滤出用户数量在4085以内的分组结果。观察图形发现,很多分组的人群非常少,甚至没有人
基于RFM分组结果的分析 通过RFM分组的Excel结果数据,我们将更进一步确定要分析的主要目标群体。 我们打开导出的
序
序
sales_rfm_score.xlsx,然后建立数据透视表,从Excel中得出的数据可以看出,与我们用Python代码分析的结果是一致的
程
程
马
马
黑
RFM用户特征分析
经过上面的分析,得到了要分析的重点客户群体。可根据用户的量级分为两类
第1类是用户群体占比超过10%的群体
第2类是占比在个位数的群体。这两类人由于量级不同,因此需要分别有针对性的策略场景。
除此以外,我们还会增加第3类人群,虽然从用户量级上小,但是单个人的价值度非常高。
第1类人群:占比超过10%的群体。由于这类人群基数大,必须采取批量操作和运营的方式落地运营策略,一般需要通过系统或产
品实现,而不能主要依赖于人工
212:可发展的一般性群体。这类群体购买新近度和订单金额一般,且购买频率低。考虑到其最大的群体基础,以及在新近
度和订单金额上都可以,因此可采取常规性的礼品兑换和赠送、购物社区活动、签到、免运费等手段维持并提升其消费状
态。
211:可发展的低价值群体。这类群体相对于212群体在订单金额上表现略差,因此在211群体策略的基础上,可以增加与订
单相关的刺激措施,例如组合商品优惠券发送、积分购买商品等
312:有潜力的一般性群体。这类群体购买新近度高,说明最近一次购买发生在很短时间之前,群体对于公司尚有比较熟悉
的接触渠道和认知状态;购物频率低,说明对网站的忠诚度一般;订单金额处于中等层级,说明其还具有可提升的空间。因
此,可以借助其最近购买的商品,为其定制一些与上次购买相关的商品,通过向上销售等策略提升购买频次和订单金额
112:可挽回的一般性群体。这类群体购买新近度较低,说明距离上次购买时间较长,很可能用户已经处于沉默或预流失、
流失阶段;购物频率低,说明对网站的忠诚度一般;订单金额处于中等层级,说明其还可能具有可提升的空间。因此,对这
部分群体的策略首先是通过多种方式(例如邮件、短信等)触达客户并挽回,然后通过针对流失客户的专享优惠(例如流失
用户专享优惠券)措施促进其消费。在此过程中,可通过增加接触频次和刺激力度的方式,增加用户的回访、复购以及订单
价值回报
213:可发展的高价值群体。这类人群发展的重点是提升购物频率,因此可指定不同的活动或事件来触达用户,促进其回访
和购买,例如不同的节日活动、每周新品推送、高价值客户专享商品等。
第2类人群:占比为1%~10%的群体。这部分人群数量适中,在落地时无论是产品还是人工都可接入
311:有潜力的低价值群体。这部分用户与211群体类似,但在购物新近度上更好,因此对其可采取相同的策略。除此以
外,在这类群体的最近接触渠道上可以增加营销或广告资源投入,通过这些渠道再次将客户引入网站完成消费。
111:这是一类在各个维度上都比较差的客户群体。一般情况下,会在其他各个群体策略和管理都落地后才考虑他们。主要
策略是先通过多种策略挽回客户,然后为客户推送与其类似的其他群体,或者当前热销的商品或折扣非常大的商品。在刺激
消费时,可根据其消费水平、品类等情况,有针对性地设置商品暴露条件,先在优惠券及优惠商品的综合刺激下使其实现消
费,再考虑消费频率以及订单金额的提升。
313:有潜力的高价值群体。这类群体的消费新近度高且订单金额高,但购买频率低,因此只要提升其购买频次,用户群体
的贡献价值就会倍增。提升购买频率上,除了在其最近一次的接触渠道上增加曝光外,与最近一次渠道相关的其他关联访问
渠道也要考虑增加营销资源。另外,213中的策略也要组合应用其中
113:可挽回的高价值群体。这类群体与112群体类似,但订单金额贡献更高,因此除了应用112中的策略外,可增加部分人
工的参与来挽回这些高价值客户,例如线下访谈、客户电话沟通等
第3类群体:占比非常少,但却是非常重要的群体
333:绝对忠诚的高价值群体。虽然用户绝对数量只有355,但由于其各方面表现非常突出,因此可以倾斜更多的资源,例
如设计VIP服务、专享服务、绿色通道等。另外,针对这部分人群的高价值附加服务的推荐也是提升其价值的重点策略
233、223和133:一般性的高价值群体。这类群体的主要着手点是提升新近购买度,即促进其实现最近一次的购买,可通过
DM、电话、客户拜访、线下访谈、微信、电子邮件等方式直接建立用户挽回通道,以挽回这部分高价值用户
322、323和332:有潜力的普通群体。这类群体最近刚完成购买,需要提升的是购买频次及购买金额。因此可通过交叉销
售、个性化推荐、向上销售、组合优惠券、打包商品销售等策略,提升其单次购买的订单金额及促进其重复购买
on
on
2.6 案例应用
针对上述得到的分析结论,会员部门采取了以下措施
分别针3类群体,按照公司实际运营需求和当前目标,制定了不同的群体落地的排期
th
th
录入数据库的RFM得分数据已经应用到其他数据模型中,成为建模输入的关键维度特征之一
2.7 案例注意点
不同品类、行业对于RFM的依赖度是有差异的,即使是一个公司在不同的发展阶段和周期下,3个维度的优先级上也会有调整
Py
大家电等消费周期较长的行业,R和M会更重要一些
Py
快消等消费周期短且快的行业,更看重R和F
具体要根据当前运营需求与业务部门沟通
对R、F、M区间的划分是一个离散化的过程,具体需要划分为几个区间需要与业务方确认
员
员
本案例划分为3个区间,结果对于业务分析而言有些多,意味着业务方需要制定十几套甚至更多的策略
如果业务方要求简化,也可以划分为2个区间,这样出来的分组数最多有8组,策略制定更加简单
具体是划分为2个还是3个,取决于当前业务方有多少资源可以投入到这个事情中来。
R、F、M的权重打分
序
序
除了案例中提到的建模方式外,结合业务经验的专家打分法也是常用的思路
虽然订单数据库中的数据质量相对较高,但可能由于数据采集、数据库同步、ETL、查询、误操作等问题,还是会导致NA值的出
现,而NA值的处理非常重要。
R、F、M三个维度的处理(包括计算、离散化、组合、转换)之前都需要注意其数据类型和格式,尤其是有关时间项的转换操作
程
应提前完成
程
小结
马
马
RFM模型是经典的一种用户分群方法,操作起来比较简单,如果数据量不是很大的时候,直接使用Excel就可以实现
RFM并不是在所有业务场景下都可以使用,一般用于零售行业(复购率相对高的行业)
使用Python的cut方法对数据进行分组,需要注意分组区间默认是左开右闭
使用Pyecharts可以方便的绘制出可以交互的3D图,在修改弹出提示信息内容时,需要注意字符串拼接的格式
黑