数据科学 Pandas数据分析讲义

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 189

00_数据科学概述

学习目标
知道数据科学相关岗位和技能要求

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在处理大数据的时候速度相对较慢

Excel,Power BI 和Tableau 需要付费购买授权

Python作为热门编程语言,功能远比Excel,PowerBI,Tableau等软件强大

Python跨平台,Windows,MacOS,Linux都可以运行

与R语言比较
Python在处理海量数据的时候比R语言效率更高
Python的工程化能力更强,应用领域更广泛,R专注于统计与数据分析领域
Python在非结构化数据(文本,音视频,图像)和深度学习领域比R更具有优势
在数据分析相关开源社区,python相关的内容远多于R语言

2 常用Python数据分析开源库介绍
2.1 Numpy

NumPy(Numerical Python) 是 Python 语言的一个扩展程序库

是一个运行速度非常快的数学库,主要用于数组计算,包含:
一个强大的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 上

2.6 Jupyter Notebook

Jupyter Notebook是一个开源Web应用程序,使用Jupyter Notebook可以创建和共享


代码
数学公式
可视化图表
笔记文档
Jupyter Notebook用途

数据清理和转换
数值模拟
统计分析
数据可视化
机器学习等
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 安装

conda install 包名字


th

th
可以通过pip install 安装

pip install 包名字


Py

安装其他包速度慢可以指定国内镜像
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/

pip install 包名 -i https://mirrors.aliyun.com/pypi/simple/ #通过阿里云镜像安装



2.3 Anaconda的虚拟环境管理
虚拟环境的作用

很多开源库版本升级后API有变化,老版本的代码不能在新版本中运行

将不同Python版本/相同开源库的不同版本隔离
不同版本的代码在不同的虚拟环境中运行
通过Anaconda界面创建虚拟环境


通过命令行创建虚拟环境
conda create -n 虚拟环境名字 python=python版本 #创建虚拟环境
conda activate 虚拟环境名字 #进入虚拟环境
conda deactivate 虚拟环境名字 #退出虚拟环境
conda remove -n 虚拟环境名字 --all #删除虚拟环境

3 Jupyter Notebook的使用

3.1 启动 Jupyter Notebook


通过Anaconda启动 Jupyter Notebook

on

on
th

th
Py

通过终端启动 Jupyter Notebook


Py
conda activate 虚拟环境名字
jupyter notebook


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库

#进入到虚拟环境中

conda activate 虚拟环境名字


#安装 jupyter_contrib_nbextensions

pip install jupyter_contrib_nbextensions
jupyter contrib nbextension install --user --skip-running-check


安装结束后启动jupyter notebook

配置扩展功能

在原来的基础上勾选: “Table of Contents” 以及 “Hinterland”


3.3 Jupyter Notebook中使用Markdown
在命令模式中,按M即可进入到Markdown编辑模式

使用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文件

csv文件 Comma-Separated Values

df = pd.read_csv('data/movie.csv') # 加载movie.csv文件
df.head() # 展示前5条数据

加载TSV文件
on

on
tsv文件 Tab-Separated Values

# 参数1 要加载的文件路径,参数2sep 传入分隔符,默认是',' '\t'制表符


df = pd.read_csv('data/gapminder.tsv',sep='\t')
print(df)
th

th
输出结果

country continent year lifeExp pop gdpPercap


Py

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

[1704 rows x 6 columns]

可以通过Python的内置函数type查看返回的数据类型


type(df)

输出结果


pandas.core.frame.DataFrame

每个dataframe都有一个shape属性,可以获取DataFrame的行数,列数

注:shape是属性 不是方法 不可以使用df.shape() 会报错

df.shape

输出结果

(1704, 6)

可以通过DataFrame的columns属性 获取DataFrame中的列名

df.columns

输出结果

Index(['country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap'], dtype='object')

如何获取每一列的数据类型?

与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类型 说明

Object string 字符串类型

int64 int 整形
Py

float64 float
Py
浮点型

datetime64 datetime 日期时间类型,python中需要加载

3 查看部分数据


3.1 根据列名加载部分列数据


加载一列数据,通过df['列名']方式获取

country_df = df['country']
#获取数据前5行
country_df.head()


输出结果

0 Afghanistan


1 Afghanistan
2 Afghanistan
3 Afghanistan
4 Afghanistan

Name: country, dtype: object

通过列名加载多列数据,通过df[['列名1','列名2',...]]

注意这里是两层[] 可以理解为 df[列名的list]

subset = df[['country','continent','year']]
#打印后五行数据
print(subset.tail())

输出结果

country continent year


1699 Zimbabwe Africa 1987
1700 Zimbabwe Africa 1992
1701 Zimbabwe Africa 1997
1702 Zimbabwe Africa 2002
1703 Zimbabwe Africa 2007

3.2 按行加载部分数据
loc:通过行索引标签获取指定行数据
#先打印前5行数据 观察第一列
print(df.head())

显示结果

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

上述结果中发现,最左边一列是行号,这一列没有列名的数据是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

# 获取最后一行 通过shape 获取一共有多少行



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方法获取最后一行数据

print(df.tail(n=1)) #tail方法默认输出一行 传入n=1控制只显示1行

输出结果

country continent year lifeExp pop gdpPercap


1703 Zimbabwe Africa 2007 43.487 12311143 469.709298

注意:df.loc 和 df.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:通过索引标签获取指定多行数据

print(df.loc[[0, 99, 999]])

输出结果

country continent year lifeExp pop gdpPercap


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 : 通过行号获取行数据
在当前案例中,使用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]])

输出结果

country continent year lifeExp pop gdpPercap



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())

输出结果

year pop gdpPercap


0 1952 8425333 779.445314
1 1957 9240934 820.853030
on

on
2 1962 10267083 853.100710
3 1967 11537966 836.197138
4 1972 13079460 739.981106

如果loc 和 iloc 传入的参数弄混了,会报错

loc 只能接受行/列 的名字, 不能传入索引


th

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())


输出结果

IndexError: .iloc requires numeric indexers, got ['year' 'pop']


通过range 生成序号,结合iloc 获取连续多列数据



tmp_range = list(range(5))
print(tmp_range)


输出结果

[0, 1, 2, 3, 4]

subset = df.iloc[:,tmp_range]
print(subset.head())

输出结果

country continent year lifeExp pop


0 Afghanistan Asia 1952 28.801 8425333
1 Afghanistan Asia 1957 30.332 9240934
2 Afghanistan Asia 1962 31.997 10267083
3 Afghanistan Asia 1967 34.020 11537966
4 Afghanistan Asia 1972 36.088 13079460

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())

输出结果

lifeExp pop gdpPercap


0 28.801 8425333 779.445314
1 30.332 9240934 820.853030
2 31.997 10267083 853.100710
3 34.020 11537966 836.197138
4 36.088 13079460 739.981106
on

on
获取第0,2,4列

subset = df.iloc[:,0:6:2]
print(subset.head())
th

th
输出结果

country year pop


0 Afghanistan 1952 8425333
1 Afghanistan 1957 9240934
Py

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])

输出结果

TypeError: cannot do label indexing on <class 'pandas.core.indexes.base.Index'> with these indexers


[0] of <class 'int'>

获取多行多列

可以把获取单行单列的语法和获取多行多列的语法结合起来使用
获取 第一列,第四列,第六列(country,lifeExp,gdpPercap) 数据中的第1行,第100行和第1000行

print(df.iloc[[0,99,999],[0,3,5]])

输出结果

country lifeExp gdpPercap


0 Afghanistan 28.801 779.445314
99 Bangladesh 43.453 721.186086
999 Mongolia 51.253 1226.041130

在实际工作中,获取某几列数据的时候,建议传入实际的列名,使用列名的好处:
增加代码的可读性
避免因列顺序的变化导致取出错误的列数据

print(df.loc[[0,99,999],['country','lifeExp','gdpPercap']])

输出结果

country lifeExp gdpPercap


0 Afghanistan 28.801 779.445314
99 Bangladesh 43.453 721.186086
999 Mongolia 51.253 1226.041130

注意:可以在loc 和 iloc 属性的行部分使用切片获取数据

print(df.loc[2:6,['country','lifeExp','gdpPercap']])

输出结果

country lifeExp gdpPercap


2 Afghanistan 31.997 853.100710
3 Afghanistan 34.020 836.197138
4 Afghanistan 36.088 739.981106
5 Afghanistan 38.438 786.113360
6 Afghanistan 39.854 978.011439
on

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中,传入列名获取我们感兴趣的数据,并进行进一步计算

计算每一年的平均预期寿命,我们需要用到 lifeExp 这一列


我们可以使用上一小节介绍的方法获取分组之后数据中的一列

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>

返回结果为一个 SeriesGroupBy (只获取了DataFrameGroupBy中的一列),其内容是分组后的数据


on

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

上面的例子只是对一列 lifeExp 进行了分组求平均,如果想对多列值进行分组聚合代码也类似


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

Europe 64.408500 5661.057435


Oceania 69.255000 10298.085650
1957 Africa 41.266346 1385.236062
Americas 55.960280 4616.043733
Asia 49.318544 5787.732940
Europe 66.703067 6963.012816
Oceania 70.295000 11598.522455
1962 Africa 43.319442 1598.078825
Americas 58.398760 4901.541870
Asia 51.563223 5729.369625
Europe 68.539233 8365.486814
Oceania 71.085000 12696.452430
1967 Africa 45.334538 2050.363801
Americas 60.410920 5668.253496
Asia 54.663640 5971.173374
Europe 69.737600 10143.823757
Oceania 71.310000 14495.021790
1972 Africa 47.450942 2339.615674
Americas 62.394920 6491.334139
Asia 57.319269 8187.468699
Europe 70.775033 12479.575246
Oceania 71.910000 16417.333380
1977 Africa 49.580423 2585.938508
Americas 64.391560 7352.007126
Asia 59.610556 7791.314020
Europe 71.937767 14283.979110
Oceania 72.855000 17283.957605
1982 Africa 51.592865 2481.592960
Americas 66.228840 7506.737088
Asia 62.617939 7434.135157
Europe 72.806400 15617.896551
Oceania 74.290000 18554.709840
1987 Africa 53.344788 2282.668991
Americas 68.090720 7793.400261
Asia 64.851182 7608.226508
Europe 73.642167 17214.310727
Oceania 75.320000 20448.040160
1992 Africa 53.629577 2281.810333
Americas 69.568360 8044.934406
Asia 66.537212 8639.690248
Europe 74.440100 17061.568084
Oceania 76.945000 20894.045885
1997 Africa 53.598269 2378.759555
Americas 71.150480 8889.300863
Asia 68.020515 9834.093295
Europe 75.505167 19076.781802
Oceania 78.190000 24024.175170
2002 Africa 53.325231 2599.385159
Americas 72.422040 9287.677107
Asia 69.233879 10174.090397
Europe 76.700600 21711.732422
Oceania 79.740000 26938.778040
on

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))


显示结果

year continent lifeExp gdpPercap


0 1952 Africa 39.135500 1252.572466


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 分组频数计算
在数据分析中,一个常见的任务是计算频数

可以使用 nunique 方法 计算Pandas Series的唯一值计数


可以使用 value_counts 方法来获取Pandas Series 的频数统计
在数据中,每个大洲列出了多少个国家和地区?

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参数 来指定行索引

s = pd.Series(['Wes McKinney','Male'],index = ['Name','Gender'])


print(s)

输出结果

Name Wes McKinney


Gender Male
dtype: object

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()

输出结果

year categor= overallMotivation firstname surname motivation share

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

可以通过 index 和 values属性获取行索引和值

first_row.index

输出结果

Index(['year', 'category', 'overallMotivation', 'firstname', 'surname',


'motivation', 'share'],
dtype='object')

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,可以进行常见计算

share = data.share # 从DataFrame中 获取Share列(几人获奖)返回Series


share.mean() #计算几人获奖的平均值

输出结果

1.982665222101842

share.max() # 计算最大值

输出结果

share.min() # 计算最小值

输出结果

1
share.std() # 计算标准差

输出结果

0.9324952202244597

通过value_counts()方法,可以返回不同值的条目数量

movie = pd.read_csv('movie.csv') # 加载电影数据


director = movie['director_name'] # 从电影数据中获取导演名字 返回Series
actor_1_fb_likes = movie['actor_1_facebook_likes'] # 从电影数据中取出主演的facebook点赞数
director.head() #查看导演Series数据

输出结果

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

2.4 Series 的运算


Series和数值型变量计算时,变量会与Series中的每个元素逐一进行计算

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()

输出结果

num_critic_for_reviews duration director_facebook_likes actor_3_facebook_likes actor_1_facebook_likes gross num_voted_users cast_total_facebook_likes



facenumber_in_poster num_user_for_reviews budget title_year actor_2_facebook_likes imdb

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

2006 rows × 28 columns

可以传入布尔值的列表,来获取部分数据,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

输出结果

Name Born Died Age Occupation

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

Florence NightingaleFlorence 1820-05-121820-05- 1910-08-131910-08-


2 180 NurseNurse
Nightingale 12 13

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

输出结果

Name Born Died Age Occupation



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

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

两个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

Florence NightingaleFlorence 1820-05-121820-05- 1910-08-131910-08-


2 180.0 NurseNurse
Nightingale 12 13

1867-11-071867-11- 1934-07-041934-07-
3 Marie CurieMarie Curie 132.0 ChemistChemist
07 04

4 NaN NaN NaN NaN NaN

5 NaN NaN NaN NaN NaN

6 NaN NaN NaN NaN NaN

7 NaN NaN NaN NaN NaN

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

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

4916 rows × 28 columns



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

Spectre Color Sam Mendes


The Dark
Christopher
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

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

4916 rows × 27 columns


加载数据的时候,可以通过通过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

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

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

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

4916 rows × 27 columns

通过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

A Plague So Benjamin Maxwell


4913 Color 13.0 76.0 0.0 0.0 0.0 NaN ... 3.0 English USA NaN 1400.0 2013.0
Pleasant Roberds Moody

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

4916 rows × 28 columns

4.2 DataFrame修改行名和列名
DataFrame创建之后,可以通过rename()方法对原有的行索引名和列名进行修改

movie = pd.read_csv('data/movie.csv', index_col='movie_title')


movie.index[:5]
on

on
输出结果

Index(['Avatar', 'Pirates of the Caribbean: At World's End', 'Spectre',


'The Dark Knight Rises', 'Star Wars: Episode VII - The Force Awakens'],
dtype='object', name='movie_title')
th

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

James Joel David Action|Adventure|Fantasy|Sci-


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属性提取出来,修改之后,再赋值回去

movie = pd.read_csv('data/movie.csv', index_col='movie_title')


index = movie.index
columns = movie.columns
index_list = index.tolist()
column_list = columns.tolist()

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

James Joel David Action|Adventure|Fantasy|Sci-


Ratava 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 Fi

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')

使用insert()方法插入列 loc 新插入的列在所有列中的位置(0,1,2,3...) column=列名 value=值


th

th
movie.insert(loc=0,column='profit',value=movie['gross'] - movie['budget'])
movie

输出结果
Py

profit color director_name num_critic_for_reviews duration director_facebook_likes


Py
actor_3_facebook_likes actor_2_name actor_1_facebook_likes gross ... language country content_rating budget title_year actor_2_facebook_likes imdb_score a

James Joel David


0 523505847.0 Color 723.0 178.0 0.0 855.0 1000.0 760505847.0 ... English USA PG-13 237000000.0 2009.0 936.0 7.9
Cameron Moore

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

4916 rows × 30 columns

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

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

注意 pandas读写excel需要额外安装如下三个包

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xlwt


pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xlrd

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

输出结果

Index(['INSTNM', 'CITY', 'STABBR', 'HBCU', 'MENONLY', 'WOMENONLY', 'RELAFFIL',


'SATVRMID', 'SATMTMID', 'DISTANCEONLY', 'UGDS', 'UGDS_WHITE',
'UGDS_BLACK', 'UGDS_HISP', 'UGDS_ASIAN', 'UGDS_AIAN', 'UGDS_NHPI',
'UGDS_2MOR', 'UGDS_NRA', 'UGDS_UNKN', 'PPTUG_EF', 'CURROPER', 'PCTPELL',
'PCTFLOAN', 'UG25ABV', 'MD_EARN_WNE_P10', 'GRAD_DEBT_MDN_SUPP'],
dtype='object')
on

on
列名 字段含义

INSTNM 大学名称

CITY 所在城市
th

th
STABBR 所在州简称

HBCU 历史上的黑人学员和大学

MENONLY 0/1 只有男学生


Py

WOMENONLY
Py
0/1 只有女学生

RELAFFIL 0/1 宗教信仰

SATVRMID SAT考试 Verbal分数中位数



SATMTMID SAT考试 数学分数中位数

DISTANCEONLY 只接受远程教育学生


UGDS 本科招生

UGDS_WHITE 本科生白人比例

UGDS_BLACK 本科生黑人比例

UGDS_HISP 本科生拉丁裔比例

UGDS_ASIAN 本科生亚裔比例

UGDS_AIAN 本科生美洲印第安人/阿拉斯加土著比例

UGDS_NHPI 本科生夏威夷/太平洋群岛土著比例

UGDS_2MOR 本科生混血比例

UGDS_NRA 本科生中留学生比例

UGDS_UNKN 本科生未知族裔比例

PPTUG_EF 非全日制学生比例

CURROPER 0/1 正在运营

PCTPELL 佩尔资助计划学生比例

PCTFLOAN 学费贷款学生比例

UG25ABV 年龄大于25岁的学生比例

MD_EARN_WNE_P10 入学10年后收入中位数

GRAD_DEBT_MDN_SUPP 毕业生债务中位数

- 查看数据行列数

college.shape

输出结果

(7535, 27)
统计数值列,并进行转置

college.describe().T

输出结果

count mean std min 25% 50% 75% max

HBCU 7164.0 0.014238 0.118478 0.0 0.000000 0.00000 0.000000 1.0000

MENONLY 7164.0 0.009213 0.095546 0.0 0.000000 0.00000 0.000000 1.0000

WOMENONLY 7164.0 0.005304 0.072642 0.0 0.000000 0.00000 0.000000 1.0000

RELAFFIL 7535.0 0.190975 0.393096 0.0 0.000000 0.00000 0.000000 1.0000

SATVRMID 1185.0 522.819409 68.578862 290.0 475.000000 510.00000 555.000000 765.0000

SATMTMID 1196.0 530.765050 73.469767 310.0 482.000000 520.00000 565.000000 785.0000

DISTANCEONLY 7164.0 0.005583 0.074519 0.0 0.000000 0.00000 0.000000 1.0000

UGDS 6874.0 2356.837940 5474.275871 0.0 117.000000 412.50000 1929.500000 151558.0000

UGDS_WHITE 6874.0 0.510207 0.286958 0.0 0.267500 0.55570 0.747875 1.0000

UGDS_BLACK 6874.0 0.189997 0.224587 0.0 0.036125 0.10005 0.257700 1.0000

UGDS_HISP 6874.0 0.161635 0.221854 0.0 0.027600 0.07140 0.198875 1.0000

UGDS_ASIAN 6874.0 0.033544 0.073777 0.0 0.002500 0.01290 0.032700 0.9727

UGDS_AIAN 6874.0 0.013813 0.070196 0.0 0.000000 0.00260 0.007300 1.0000

UGDS_NHPI 6874.0 0.004569 0.033125 0.0 0.000000 0.00000 0.002500 0.9983

UGDS_2MOR 6874.0 0.023950 0.031288 0.0 0.000000 0.01750 0.033900 0.5333

UGDS_NRA 6874.0 0.016086 0.050172 0.0 0.000000 0.00000 0.011700 0.9286

UGDS_UNKN 6874.0 0.045181 0.093440 0.0 0.000000 0.01430 0.045400 0.9027


on

on
PPTUG_EF 6853.0 0.226639 0.246470 0.0 0.000000 0.15040 0.376900 1.0000

CURROPER 7535.0 0.923291 0.266146 0.0 1.000000 1.00000 1.000000 1.0000

PCTPELL 6849.0 0.530643 0.225544 0.0 0.357800 0.52150 0.712900 1.0000

PCTFLOAN 6849.0 0.522211 0.283616 0.0 0.332900 0.58330 0.745000 1.0000

UG25ABV 6718.0 0.410021 0.228939 0.0 0.241500 0.40075 0.572275 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

INSTNM 7535 7535 Institute of American Indian and Alaska Native... 1



CITY 7535 2514 New York 87

STABBR 7535 59 CA 773

MD_EARN_WNE_P10 6413 598 PrivacySuppressed 822


GRAD_DEBT_MDN_SUPP 7503 2038 PrivacySuppressed


程 1510

通过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()

输出结果

movie_title imdb_score budget

0 Avatar 7.9 237000000.0

1 Pirates of the Caribbean: At World's End 7.1 300000000.0

2 Spectre 6.8 245000000.0


on

on
3 The Dark Knight Rises 8.5 250000000.0

4 Star Wars: Episode VII - The Force Awakens 7.1 NaN

用nlargest方法,选出imdb_score分数最高的100个
th

th
movie2.nlargest(100, 'imdb_score').head()

输出结果
Py

movie_title
Py
imdb_score budget

2725 Towering Inferno 9.5 NaN

1920 The Shawshank Redemption 9.3 25000000.0



3402 The Godfather 9.2 6000000.0

2779 Dekalog 9.1 NaN



4312 Kickboxer: Vengeance 9.1 17000000.0

使用nsmallest方法再从中挑出预算最小的五部

movie2.nlargest(100, 'imdb_score').nsmallest(5, 'budget')



输出结果

movie_title imdb_score budget


4804 Butterfly Girl 8.7 180000.0

4801 Children of Heaven 8.5 180000.0


4706 12 Angry Men 8.9 350000.0

4550 A Separation 8.4 500000.0

4636 The Other Dream Team 8.4 500000.0

2.2 通过排序选取每组的最大值——找到每年imdb评分最高的电影
sort_values 按照年排序,ascending 升序排列

movie2 = movie[['movie_title', 'title_year', 'imdb_score']]


movie2.sort_values('title_year', ascending=False).head()

输出结果
movie_title title_year imdb_score

3884 The Veil 2016.0 4.7

2375 My Big Fat Greek Wedding 2 2016.0 6.1

2794 Miracles from Heaven 2016.0 6.8

92 Independence Day: Resurgence 2016.0 5.5

153 Kung Fu Panda 3 2016.0 7.2

同时对'title_year','imdb_score' 两列进行排序

movie3 = movie2.sort_values(['title_year','imdb_score'], ascending=False)


movie3.head()

输出结果

movie_title title_year imdb_score

4312 Kickboxer: Vengeance 2016.0 9.1

4277 A Beginner's Guide to Snuff 2016.0 8.7

3798 Airlift 2016.0 8.5


on

on
27 Captain America: Civil War 2016.0 8.2

98 Godzilla Resurgence 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

4312 Kickboxer: Vengeance 2016.0 9.1



3745 Running Forever 2015.0 8.6

4369 Queen of the Mountains 2014.0 8.7

3935 Batman: The Dark Knight Returns, Part 2 2013.0 8.4



3 The Dark Knight Rises 2012.0 8.5

2.3 提取出每年,每种电影分级中预算少的电影——sort_values多列排序


多列排序时,ascending 参数传入一个列表,排序一一对应

movie4 = movie[['movie_title', 'title_year', 'content_rating', 'budget']]



# 多列排序,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)

输出结果

movie_title title_year content_rating budget

4026 Compadres 2016.0 R 3000000.0

4658 Fight to the Finish 2016.0 PG-13 150000.0

4661 Rodeo Girl 2016.0 PG 500000.0

3252 The Wailing 2016.0 Not Rated NaN

4659 Alleluia! The Devil's Carnival 2016.0 NaN 500000.0

4731 Bizarre 2015.0 Unrated 500000.0

812 The Ridiculous 6 2015.0 TV-14 NaN

4831 The Gallows 2015.0 R 100000.0

4825 Romantic Schemer 2015.0 PG-13 125000.0

3796 R.L. Stine's Monsterville: The Cabinet of Souls 2015.0 PG 4400000.0

3 简单数据分析练习(租房数据)
载入数据

import pandas as pd
house_data = pd.read_csv('data/LJdata.csv')

把列名替换成英文

#查看原始列名
house_data.columns

输出结果

Index(['区域', '地址', '标题', '户型', '面积', '价格', '楼层', '建造时间', '朝向', '更新时间', '看房人数','备
注', '链接地址'],dtype='object')

house_data.columns = ['district', 'address', 'title', 'house_type', 'area', 'price', 'floor',


'build_time', 'direction', 'update_time', 'view_num', 'extra_info', 'link']

查看数据基本情况

house_data.head()

输出结果
on

on
district address title house_type area price floor build_time direction update_time view_num extra_info link

燕莎租 亮马桥 新源街 精装两居 交通便利 看 50平 中楼层 1981年建板 https://bj.lianjia.com/zufa


0 新源街 2室1厅 5800 南 2017.07.21 26 随时看房 精装修 集中供暖
房 房方便 随时入住 米 (共6层) 楼 ng/101101803342.html

望京租 澳洲康 79平 中楼层 2005年建板 距离14号线(东段)东湖渠站731 https://bj.lianjia.com/zufa


1 澳洲康都东向精致两居室........... 2室1厅 7800 东 2017.07.23 33
房 都 米 (共28层) 塔结合 米 随时看房 精装修 集中供暖 ng/101101753126.html

广安门 远见名 远见名苑 东向两居室 独立小区环境 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

团结湖 团结湖 团结湖北口近地铁高楼层朝南向精装 63平 高楼层 1982年建塔 距离10号线团结湖站88米 随时 https://bj.lianjia.com/zufa


4 2室1厅 6400 南 2017.07.26 30
租房 北口 修正规两居室 米 (共16层) 楼 看房 精装修 集中供暖 ng/101101781083.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

direction 2760 non-null object



update_time 2760 non-null object
view_num 2760 non-null int64
extra_info 2760 non-null object
link 2760 non-null object


dtypes: int64(2), object(11)
memory usage: 280.4+ KB

house_data.shape

输出结果

(2760, 13)

house_data.describe()

输出结果
price view_num

count 2760.000000 2760.000000

mean 7570.800725 13.448913

std 6316.204986 12.746202

min 1300.000000 0.000000

25% 4500.000000 4.000000

50% 6000.000000 10.000000

75% 8500.000000 19.000000

max 210000.000000 122.000000

找到租金最低,和租金最高的房子

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()]

sort_values按照值排序 参数by 传入列名 参数 ascending(升序)


on

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

劲松租 武圣西 73平 低楼层 1986年建 距离10号线潘家园站965米 https://bj.lianjia.com/zufa


347 武圣西里正规三居室,低楼层,看房方便 3室1厅 6500 东南 北 2017.07.27 0
房 里 米 (共6层) 板楼 随时看房 ng/101101862495.html

广安门 恒昌花 159 高楼层 1999年建 距离7号线达官营站292米 https://bj.lianjia.com/zufa


1790 广安门恒昌花园西向三居室集中供暖 3室2厅 13500 西 2017.07.27 12
租房 园 平米 (共28层) 塔楼 随时看房 集中供暖 ng/101101692254.html

2405
武夷花
园租房
月亮城

月亮城堡精装修正规一居 南北通透 户
型好
1室1厅
102
平米
4500
低楼层
(共11层)
2005年建
板塔结合
南北 2017.07.27 28

随时看房 精装修 集中供暖
https://bj.lianjia.com/zufa
ng/101101495373.html

常营租 北京像 48平 低楼层 2012年建 距离6号线草房站66米 随时 https://bj.lianjia.com/zufa


2639 北京像素 地铁6号线草房 北向开间 1房间1卫 3750 北 2017.07.27 3
房 素南区 米 (共23层) 板塔结合 看房 精装修 集中供暖 ng/101101852394.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()

输出结果

array(['2017.07.21', '2017.07.23', '2017.07.20', '2017.07.25',


'2017.07.26', '2017.07.16', '2017.07.22', '2017.07.24',
'2017.07.27', '2017.07.19', '2017.07.14', '2017.07.15',
'2017.07.17', '2017.07.18'], dtype=object)

看房人数

house_data['view_num'].mean() #平均值
house_data['view_num'].median() #中位数

不同看房人数的房源数量,as_index = False 分组字段不作为行索引(默认为True)

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 功能是在jupyter notebook中内嵌绘图,并且可以省略掉 plt.show()

%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

951 清芷园 246



369 卡布其诺 245

938 润枫水尚 217

1149 芍药居北里 194


743 新康园 186

出租房源最多的小区

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

从上面的结果中可以看到,concat函数把3个DataFrame连接在了一起(简单堆叠),可以通过 iloc ,loc等方法取出连接后的数据


的子集

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

3 a11 b11 c11 d11


Py
使用concat连接DataFrame和Series

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

0 NaN NaN NaN NaN n1

1 NaN NaN NaN NaN n2

2 NaN NaN NaN NaN n3

3 NaN NaN NaN NaN n4

上面的结果中包含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

传入axis = 'columns' ,连接后的DataFrame按列添加,并匹配各自行索引,缺失值用NaN表示


Py

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函数
加载数据:

from sqlalchemy import create_engine


#需要安装sqlalchemy pip install sqlalchemy
engine = create_engine('sqlite:///data/chinook.db')
#连接数据库
tracks = pd.read_sql_table('tracks', engine)
tracks.head()

显示结果:
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

1 2 Balls to the Wall 2 2 1 None 342562 5510424 0.99

F. Baltes, S. Kaufman, U. Dirkscneider & W.


2 3 Fast As a Shark 3 2 1 230619 3990994 0.99
Ho...

F. Baltes, R.A. Smith-Diesel, S. Kaufman, U.


3 4 Restless and Wild 3 2 1 252051 4331779 0.99
D...

4 5 Princess of the Dawn 3 2 1 Deaffy & R.A. Smith-Diesel 375418 6290521 0.99

read_sql_table函数可以从数据库中读取表,第一个参数是表名,第二个参数是数据库连接对象

genres = pd.read_sql_table('genres', engine)


print(genres)

显示结果:

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

显示结果:

TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice



0 1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.99

62 63 Desafinado 8 1 2 None 185338 5990473 0.99

76 77 Enter Sandman 9 1 3 Apocalyptica 221701 7286305 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

204 205 Jorge Da Capadócia 21 1 7 Jorge Ben 177397 5842196 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

359 360 Vai-Vai 2001 32 1 10 None 276349 9402241 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

genre_track = genres.merge(tracks_subset[['TrackId','GenreId', 'Milliseconds']], on='GenreId',


how='left')
print(genre_track)

显示结果:

GenreId Name TrackId Milliseconds


0 1 Rock 1.0 343719.0
1 2 Jazz 63.0 185338.0
2 3 Metal 77.0 221701.0
3 4 Alternative & Punk 99.0 255529.0
4 5 Rock And Roll 111.0 147591.0
5 6 Blues 194.0 140434.0
6 7 Latin 205.0 177397.0
7 8 Reggae 282.0 249808.0
8 9 Pop 323.0 205479.0
9 10 Soundtrack 360.0 276349.0
10 11 Bossa Nova NaN NaN
11 12 Easy Listening NaN NaN
12 13 Heavy Metal NaN NaN
13 14 R&B/Soul NaN NaN
14 15 Electronica/Dance NaN NaN
15 16 World NaN NaN
16 17 Hip Hop/Rap NaN NaN
17 18 Science Fiction NaN NaN
18 19 TV Shows NaN NaN
19 20 Sci Fi & Fantasy NaN NaN
20 21 Drama NaN NaN
21 22 Comedy NaN NaN
22 23 Alternative NaN NaN
23 24 Classical NaN NaN
24 25 Opera NaN NaN

genre_track = genres.merge(tracks_subset[['TrackId','GenreId', 'Milliseconds']], on='GenreId',


how='right')
print(genre_track)
on

on
显示结果:

GenreId Name TrackId Milliseconds


0 1 Rock 1 343719
1 2 Jazz 63 185338
2 3 Metal 77 221701
th

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的全部数据

genre_track = genres.merge(tracks[['TrackId','GenreId', 'Milliseconds']], on='GenreId', how='left')



print(genre_track)

显示结果:

GenreId Name TrackId Milliseconds



0 1 Rock 1 343719
1 1 Rock 2 342562
2 1 Rock 3 230619


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

Rock And Roll 00:02:14


Sci Fi & Fantasy 00:48:31
Science Fiction 00:43:45
Soundtrack 00:04:04
TV Shows 00:35:45
World 00:03:44
Name: Milliseconds, dtype: timedelta64[ns]

计算每名用户的平均消费

从三张表中获取数据,用户表获取用户id,姓名
发票表,获取发表id,用户id
发票详情表,获取发票id,单价,数量

cust = pd.read_sql_table('customers',engine,columns=['CustomerId', 'FirstName', 'LastName'])


invoice = pd.read_sql_table('invoices',engine,columns=['InvoiceId','CustomerId'])
ii = pd.read_sql_table('invoice_items',engine,columns=['InvoiceId', 'UnitPrice', 'Quantity'])

根据用户Id('CustomerId')合并用户表和发票表,根据发票Id ('InvoiceId')合并发票和发票详情表

cust_inv = cust.merge(invoice, on='CustomerId').merge(ii, on='InvoiceId')


print(cust_inv.head())

显示结果:
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方法 创建新列

total = cust_inv['Quantity'] * cust_inv['UnitPrice']


cust_inv = cust_inv.assign(Total = total)
print(cust_inv.head())

显示结果:

CustomerId FirstName LastName InvoiceId UnitPrice Quantity Total


0 1 Luís Gonçalves 98 1.99 1 1.99
1 1 Luís Gonçalves 98 1.99 1 1.99
2 1 Luís Gonçalves 121 0.99 1 0.99
3 1 Luís Gonçalves 121 0.99 1 0.99
4 1 Luís Gonçalves 121 0.99 1 0.99

按照用户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

显示结果:

Symbol Shares Low High

0 AAPL 50 120 140

1 GE 100 30 40

2 IBM 87 75 95

3 SLB 20 55 85

4 TXN 500 15 23

5 TSLA 100 100 300

stocks_2018

显示结果:
Symbol Shares Low High

0 AAPL 40 135 170

1 AMZN 8 900 1125

2 TSLA 50 220 400

join合并,依据两个DataFrame的行索引,如果合并的两个数据有相同的列名,需要通过lsuffix,和rsuffix,指定合并后的列名
的前缀

stocks_2016.join(stocks_2017, lsuffix='_2016', rsuffix='_2017', how='outer')

显示结果:

Symbol_2016 Shares_2016 Low_2016 High_2016 Symbol_2017 Shares_2017 Low_2017 High_2017

0 AAPL 80.0 95.0 110.0 AAPL 50 120 140

1 TSLA 50.0 80.0 130.0 GE 100 30 40

2 WMT 40.0 55.0 70.0 IBM 87 75 95

3 NaN NaN NaN NaN SLB 20 55 85

4 NaN NaN NaN NaN TXN 500 15 23

5 NaN NaN NaN NaN TSLA 100 100 300

将两个DataFrame的Symbol设置为行索引,再次join数据
on

on
stocks_2016.set_index('Symbol').join(stocks_2018.set_index('Symbol'),lsuffix='_2016', rsuffix='_2018')

显示结果:

Shares_2016 Low_2016 High_2016 Shares_2018 Low_2018 High_2018


th

th
Symbol

AAPL 80 95 110 40.0 135.0 170.0

TSLA 50 80 130 50.0 220.0 400.0

WMT 40 55 70 NaN NaN NaN


Py

将一个DataFrame的Symbol列设置为行索引,与另一个DataFrame的Symbol列进行join
Py
stocks_2016.join(stocks_2018.set_index('Symbol'),lsuffix='_2016', rsuffix='_2018',on='Symbol')


显示结果:

Symbol Shares_2016 Low_2016 High_2016 Shares_2018 Low_2018 High_2018

0 AAPL 80 95 110 40.0 135.0 170.0



1 TSLA 50 80 130 50.0 220.0 400.0

2 WMT 40 55 70 NaN NaN NaN

小结


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))

显示结果

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
6 837 MSK-4 1932-01-14
7 844 DR-1 1932-03-22
on

on
加载数据,手动指定缺失值

print(pd.read_csv('data/survey_visited.csv',na_values=[""],keep_default_na = False))
th

th
显示结果

ident site dated


0 619 DR-1 1927-02-08
Py

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

2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S

3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S

4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN 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列包含缺失值
缺失值 占比(%)

Cabin 687 77.1

Age 177 19.9

Embarked 2 0.2

test_missing= missing_values_table(test)
test_missing

显示结果

传入的数据集中共 11 列.
其中 3列包含缺失值

缺失值 占比(%)

Cabin 327 78.2

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列包含缺失值
缺失值 占比(%)

Xylene 18109 61.3

PM10 11140 37.7

NH3 10328 35.0

Toluene 8041 27.2

Benzene 5623 19.0

AQI 4681 15.9

AQI_Bucket 4681 15.9

PM2.5 4598 15.6

NOx 4185 14.2

O3 4022 13.6

SO2 3854 13.1

NO2 3585 12.1

NO 3582 12.1
on

on
CO 2059 7.0

数据中有很多缺失值,比如Xylene(二甲苯)和 PM10 有超过50%的缺失值

#查看包含缺失数据的部分
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

Name: Xylene, dtype: float64



使用ffill 填充,用时间序列中空值的上一个非空值填充


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

Name: Xylene, dtype: float64

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()


显示结果:

religion < 10-20k 30-40k 50-75k 100-150k >150k Don't know/refused

0 Agnostic 27 34 60 81 76 137


1 Atheist 12 27 37 52 35 70

2 Buddhist 27 21 30 34 33 58

3 Catholic 418 617 732 670 638 1116


4 Don’t know/refused 15 14 15 11

10 35

对于展示数据而言,这种"宽"数据没有任何问题,如第一行数据,展示了Agnostic(不可知论(者))所有的收入分布情况

从数据分析的角度,有时候我们需要把数据由"宽"数据,转换成”长”数据


pandas的melt函数可以把宽数据集,转换为长数据集

melt及时类函数也是实力函数,也就是说既可以用pd.melt, 也可使用dataframe.melt()

参数 类型 说明

frame dataframe 被 melt 的数据集名称在 pd.melt() 中使用

id_vars tuple/list/ndarray 可选项不需要被转换的列名,在转换后作为标识符列(不是索引列)

value_vars tuple/list/ndarray 可选项需要被转换的现有列如果未指明,除 id_vars 之外的其他列都被转换

var_name string variable 默认值自定义列名名称设置由 'value_vars' 组成的新的 column name

value_name string value 默认值自定义列名名称设置由 'value_vars' 的数据组成的新的 column name

col_level int/string 可选项如果列是MultiIndex,则使用此级别

使用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

3 Catholic <$10k 418

4 Don’t know/refused <$10k 15

... ... ... ...

175 Orthodox Don't know/refused 73

176 Other Christian Don't know/refused 18

177 Other Faiths Don't know/refused 71

178 Other World Religions Don't know/refused 8

179 Unaffiliated Don't know/refused 597

180 rows × 3 columns

可以更改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

3 Catholic <$10k 418



4 Don’t know/refused <$10k 15

... ... ... ...

175 Orthodox Don't know/refused 73



176 Other Christian Don't know/refused 18

177 Other Faiths Don't know/refused 71

178 Other World Religions Don't know/refused 8



179 Unaffiliated Don't know/refused 597

180 rows × 3 columns



在使用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

317 rows × 81 columns

使用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

2 2000 3 Doors Down Kryptonite 3:53 2000-04-08 wk1 81.0

3 2000 3 Doors Down Loser 4:24 2000-10-21 wk1 76.0

4 2000 504 Boyz Wobble Wobble 3:35 2000-04-15 wk1 57.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

24091 2000 matchbox twenty Bent 4:12 2000-04-29 wk76 NaN

24092 rows × 7 columns

可以将上述数据进一步处理,当我们查询任意一首歌曲信息时,会发现数据的存储有冗余的情况

bill_borad_long[bill_borad_long.track =='Loser']
on

on
显示结果:

year artist track time date.entered week rating

3 2000 3 Doors Down Loser 4:24 2000-10-21 wk1 76.0


th

th
320 2000 3 Doors Down Loser 4:24 2000-10-21 wk2 76.0

637 2000 3 Doors Down Loser 4:24 2000-10-21 wk3 72.0

954 2000 3 Doors Down Loser 4:24 2000-10-21 wk4 69.0


Py

1271 2000 3 Doors Down Loser


Py 4:24 2000-10-21 wk5 67.0

... ... ... ... ... ... ... ...

22510 2000 3 Doors Down Loser 4:24 2000-10-21 wk72 NaN



22827 2000 3 Doors Down Loser 4:24 2000-10-21 wk73 NaN

23144 2000 3 Doors Down Loser 4:24 2000-10-21 wk74 NaN

23461 2000 3 Doors Down Loser 4:24 2000-10-21 wk75 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

显示结果:

year artist track time date.entered

0 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26

1 2000 2Ge+her The Hardest Part Of ... 3:15 2000-09-02

2 2000 3 Doors Down Kryptonite 3:53 2000-04-08

3 2000 3 Doors Down Loser 4:24 2000-10-21

4 2000 504 Boyz Wobble Wobble 3:35 2000-04-15

... ... ... ... ... ...

312 2000 Yankee Grey Another Nine Minutes 3:10 2000-04-29

313 2000 Yearwood, Trisha Real Live Woman 3:55 2000-04-01

314 2000 Ying Yang Twins Whistle While You Tw... 4:19 2000-03-18

315 2000 Zombie Nation Kernkraft 400 3:30 2000-09-02

316 2000 matchbox twenty Bent 4:12 2000-04-29


317 rows × 5 columns

为上面数据添加id列

billboard_songs['id'] = range(len(billboard_songs))
billboard_songs

显示结果:

year artist track time date.entered id

0 2000 2 Pac Baby Don't Cry (Keep... 4:22 2000-02-26 0

1 2000 2Ge+her The Hardest Part Of ... 3:15 2000-09-02 1

2 2000 3 Doors Down Kryptonite 3:53 2000-04-08 2

3 2000 3 Doors Down Loser 4:24 2000-10-21 3

4 2000 504 Boyz Wobble Wobble 3:35 2000-04-15 4

... ... ... ... ... ... ...

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

315 2000 Zombie Nation Kernkraft 400 3:30 2000-09-02 315

316 2000 matchbox twenty Bent 4:12 2000-04-29 316


th

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

24088 316 wk73 NaN

24089 316 wk74 NaN


24090 316 wk75 NaN

24091 316 wk76 NaN

24092 rows × 3 columns

这样,数据拆分成两个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

24092 rows × 8 columns

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

8 Florida Banana 190

重命名列
state_fruit_tidy.columns = ['state', 'fruit', 'weight']
state_fruit_tidy

显示结果:

state fruit 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

8 Florida Banana 190

也可以使用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')


显示结果:

state fruit 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

8 Florida Banana 190

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

从上面数据中可以看出,列名中包含了数字 1,2,3 如果想把这部分信息提取到列当中,可以使用wide_to_long函数


使用wide_to_long函数时,要求 1,2,3 这样的顺序信息在列名的最后,并用分隔符隔开

#创建一个自定义函数,用来改变列名。将数字放到列名的最后
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()

显示结果:

movie_title actor_1 actor_2 actor_3 actor_facebook_likes_1 actor_facebook_likes_2 actor_facebook_likes_3

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

stubs = ['actor', 'actor_facebook_likes']


actor2_tidy = pd.wide_to_long(actor2,
stubnames=stubs,
th

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

1 Pirates of the Caribbean: At World's End 1 Johnny Depp 40000.0

2 Spectre 1 Christoph Waltz 11000.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()

显示结果:

Apple Orange Banana

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],

[94, 92, 93, 67, 64],


[86, 85, 83, 67, 80]])

score

显示结果

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],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])

使用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代

1.2 Numpy 的ndarray


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的类型

名称 描述 简写

np.bool 用一个字节存储的布尔类型(True或False) 'b'

np.int8 一个字节大小,-128 至 127 'i'

np.int16 整数,-32768 至 32767 'i2'

np.int32 整数,-2 31 至 2 32 -1 'i4'

np.int64 整数,-2 63 至 2 63 - 1 'i8'

np.uint8 无符号整数,0 至 255 'u'

np.uint16 无符号整数,0 至 65535 'u2'

np.uint32 无符号整数,0 至 2 ** 32 - 1 'u4'

np.uint64 无符号整数,0 至 2 ** 64 - 1 'u8'


on

on
np.float16 半精度浮点数:16位,正负号1位,指数5位,精度10位 'f2'

np.float32 单精度浮点数:32位,正负号1位,指数8位,精度23位 'f4'

np.float64 双精度浮点数:64位,正负号1位,指数11位,精度52位 'f8'


th

th
np.complex64 复数,分别用两个32位浮点数表示实部和虚部 'c8'

np.complex128 复数,分别用两个64位浮点数表示实部和虚部 'c16'


Py

np.object_ python对象
Py 'O'

np.string_ 字符串 'S'

np.unicode_ unicode类型


创建数组的时候指定类型

a = np.array([[1, 2, 3],[4, 5, 6]], dtype=np.float32)


a.dtype


显示结果

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

显示结果

total_bill tip sex smoker day time size

0 16.99 1.01 Female No Sun Dinner 2

1 missing 1.66 Male No Sun Dinner 3

2 21.01 3.50 Male No Sun Dinner 3

3 missing 3.31 Male No Sun Dinner 2

4 24.59 3.61 Female No Sun Dinner 4

5 missing 4.71 Male No Sun Dinner 4

6 8.77 2.00 Male No Sun Dinner 2


on

on
7 missing 3.12 Male No Sun Dinner 4

8 15.04 1.96 Male No Sun Dinner 2

9 14.78 3.23 Male No Sun Dinner 2


th

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)
... ....

ValueError: could not convert string to float: 'missing'


如果使用Pandas库中的to_numeric函数进行转换,也会得到类似的错误

pd.to_numeric(tips_sub_miss['total_bill'])

显示结果

ValueError Traceback (most recent call last)


pandas\_libs\lib.pyx in pandas._libs.lib.maybe_convert_numeric()

ValueError: Unable to parse string "missing"

During handling of the above exception, another exception occurred:

ValueError Traceback (most recent call last)


<ipython-input-9-4fcf9a4ed513> in <module>
----> 1 pd.to_numeric(tips_sub_miss['total_bill'])

~\anaconda3\lib\site-packages\pandas\core\tools\numeric.py in to_numeric(arg, errors, downcast)


148 try:
149 values = lib.maybe_convert_numeric(
--> 150 values, set(), coerce_numeric=coerce_numeric
151 )
152 except (ValueError, TypeError):
pandas\_libs\lib.pyx in pandas._libs.lib.maybe_convert_numeric()

ValueError: Unable to parse string "missing" at position 1

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 数据类型转换

Pandas除了数值型的int 和 float类型外,还有object ,category,bool,datetime类型



可以通过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\frame.py in apply(self, func, axis, raw, result_type, args,


**kwds)
6876 kwds=kwds,
6877 )
-> 6878 return op.get_result()
6879
6880 def applymap(self, func) -> "DataFrame":
~\anaconda3\lib\site-packages\pandas\core\apply.py in get_result(self)
184 return self.apply_raw()
185
--> 186 return self.apply_standard()
187
188 def apply_empty_result(self):

~\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()

TypeError: avg_3() missing 2 required positional arguments: 'y' and 'z'

从报错的信息中看到,实际上传入avg_3函数中的只有一个变量,这个变量可以是DataFrame的行也可以是DataFrame的列, 使用apply的
时候,可以通过axis参数指定按行/ 按列 传入数据

axis = 0 (默认) 按列处理


axis = 1 按行处理
on

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

该数据集有891行,15列, 其中age 和 deck 两列中包含缺失值.可以使用apply计算数据中有多少null 或 NaN值

缺失值数目

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().

上面函数中, x==20 , x 是向量, 但20是标量, 不能直接计算. 这个时候可以使用np.vectorize将函数向量化

avg_2_mod_vec = np.vectorize(avg_2_mod)
avg_2_mod_vec(df['a'],df['b'])

显示结果:

array([15., nan, 35.])

使用装饰器

@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

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

... ... ... ... ... ... ...

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

1704 rows × 6 columns

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

显示结果:

country continent year lifeExp pop gdpPercap

0 Afghanistan Asia 1952 28.801 8425333 779.445314

12 Albania Europe 1952 55.230 1282697 1601.056136

24 Algeria Africa 1952 43.077 9279525 2449.008185

36 Angola Africa 1952 30.015 4232095 3520.610273

48 Argentina Americas 1952 62.485 17876956 5911.315053

... ... ... ... ... ... ...

1644 Vietnam Asia 1952 40.412 26246839 605.066492

1656 West Bank and Gaza Asia 1952 43.160 1030585 1515.592329

1668 Yemen, Rep. Asia 1952 32.548 4963829 781.717576

1680 Zambia Africa 1952 42.038 2672000 1147.388831

1692 Zimbabwe Africa 1952 48.451 3080907 406.884115

142 rows × 6 columns


y1952.lifeExp.mean()

显示结果:

49.05761971830987

groupby 语句会针对每个不同年份重复上述过程,并把所有结果放入一个DataFrame中返回
mean函数不是唯一的聚合函数, Pandas内置了许多方法, 都可以与groupby语句搭配使用

1.2 Pandas内置的聚合方法
可以与groupby一起使用的方法和函数

Pandas方法 Numpy函数 说明

count np.count_nonzero 频率统计(不包含NaN值)

size 频率统计(包含NaN值)

mean np.mean 求平均值

std np.std 标准差

min np.min 最小值

quantile() np.percentile() 分位数


on

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

Africa 624.0 48.865330 9.150210 23.599 42.37250 47.7920 54.41150 76.442

Americas 300.0 64.658737 9.345088 37.579 58.41000 67.0480 71.69950 80.653


Asia 396.0 60.064903 11.864532 28.801 51.42625 61.7915



69.50525 82.603

Europe 360.0 71.903686 5.433178 43.585 69.57000 72.2410 75.45050 81.757

Oceania 24.0 74.326208 3.795611 69.120 71.20500 73.6650 77.55250 81.235



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

自定义函数中只有一个参数values, 但传入该函数中的数据是一组值, 需要对values进行迭代才能取出每一个值



自定义函数可以有多个参数, 第一个参数接受来自DataFrame分组这之后的值, 其余参数可自定义

# 计算全球平均预期寿命的平均值 与分组之后的平均值做差
def my_mean_diff(values,diff_value):


'''计算平均值和diff_value之差
'''
n = len(values)
sum = 0

for value in values:


sum+=value

mean = sum/n
return(mean-diff_value)
# 计算整个数据集的平均年龄


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

1952 142.0 49.057620 12.225956

1957 142.0 51.507401 12.231286

1962 142.0 53.609249 12.097245

1967 142.0 55.678290 11.718858

1972 142.0 57.647386 11.381953

1977 142.0 59.570157 11.227229

1982 142.0 61.533197 10.770618

1987 142.0 63.212613 10.556285

1992 142.0 64.160338 11.227380

1997 142.0 65.014676 11.559439

2002 142.0 65.694923 12.279823

2007 142.0 67.007423 12.073021


on

on
1.5 向agg/aggregate中传入字典
分组之后,可以对多个字段用不同的方式聚合

df.groupby('year').agg({'lifeExp':'mean','pop':'median','gdpPercap':'median'})
th

th
显示结果:

lifeExp pop gdpPercap


Py

year
Py
1952 49.057620 3943953.0 1968.528344

1957 51.507401 4282942.0 2173.220291



1962 53.609249 4686039.5 2335.439533

1967 55.678290 5170175.5 2678.334741



1972 57.647386 5877996.5 3339.129407

1977 59.570157 6404036.5 3798.609244

1982 61.533197 7007320.0 4216.228428



1987 63.212613 7774861.5 4280.300366

1992 64.160338 8688686.5 4386.085502



1997 65.014676 9735063.5 4781.825478

2002 65.694923 10372918.5 5319.804524

2007 67.007423 10517531.0 6124.371109


从聚合之后返回的DataFrame中发现, 聚合后的列名就是聚合函数的名字, 可以通过rename进行重命名

df.groupby('year').agg({'lifeExp':'mean','pop':'median','gdpPercap':'median'}).\
rename(columns={'lifeExp':'平均寿命','pop':'人口','gdpPercap':'人均Gdp'}).reset_index()

显示结果:
year 平均寿命 人口 人均Gdp

0 1952 49.057620 3943953.0 1968.528344

1 1957 51.507401 4282942.0 2173.220291

2 1962 53.609249 4686039.5 2335.439533

3 1967 55.678290 5170175.5 2678.334741

4 1972 57.647386 5877996.5 3339.129407

5 1977 59.570157 6404036.5 3798.609244

6 1982 61.533197 7007320.0 4216.228428

7 1987 63.212613 7774861.5 4280.300366

8 1992 64.160338 8688686.5 4386.085502

9 1997 65.014676 9735063.5 4781.825478

10 2002 65.694923 10372918.5 5319.804524

11 2007 67.007423 10517531.0 6124.371109

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分组填充缺失值
之前介绍了填充缺失值的各种方法,对于某些数据集,可以使用列的平均值来填充缺失值。某些情况下,可以考虑将列进行分组,分
组之后取平均再填充缺失值

tips_10 = pd.read_csv('data/tips.csv').sample(10,random_state = 42)


tips_10

显示结果:
total_bill tip sex smoker day time size

24 19.82 3.18 Male No Sat Dinner 2

6 8.77 2.00 Male No Sun Dinner 2

153 24.55 2.00 Male No Sun Dinner 4

211 25.89 5.16 Male Yes Sat Dinner 4

198 13.00 2.00 Female Yes Thur Lunch 2

176 17.89 2.00 Male Yes Sun Dinner 2

192 28.44 2.56 Male Yes Thur Lunch 2

124 12.48 2.52 Female No Thur Lunch 2

9 14.78 3.23 Male No Sun Dinner 2

101 15.38 3.00 Female Yes Fri Dinner 2

构建缺失值

#np.random.permutation 将序列乱序
tips_10.loc[np.random.permutation(tips_10.index)[:4],'total_bill'] = np.NaN
tips_10
on

on
显示结果:

total_bill tip sex smoker day time size


th

th
24 19.82 3.18 Male No Sat Dinner 2

6 8.77 2.00 Male No Sun Dinner 2

153 NaN 2.00 Male No Sun Dinner 4


Py

211 NaN 5.16


Py
Male Yes Sat Dinner 4

198 NaN 2.00 Female Yes Thur Lunch 2

176 NaN 2.00 Male Yes Sun Dinner 2



192 28.44 2.56 Male Yes Thur Lunch 2

124 12.48 2.52 Female No Thur Lunch 2


9 14.78

3.23 Male No Sun Dinner


2

101 15.38 3.00 Female Yes Fri Dinner 2

查看缺失情况


count_sex = tips_10.groupby('sex').count()
count_sex


显示结果:

sex total_bill tip smoker day time size

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

显示结果:

total_bill tip sex smoker day time size fill_total_bill

24 19.82 3.18 Male No Sat Dinner 2 19.8200

6 8.77 2.00 Male No Sun Dinner 2 8.7700

153 NaN 2.00 Male No Sun Dinner 4 17.9525


on

on
211 NaN 5.16 Male Yes Sat Dinner 4 17.9525

198 NaN 2.00 Female Yes Thur Lunch 2 13.9300

176 NaN 2.00 Male Yes Sun Dinner 2 17.9525


th

th
192 28.44 2.56 Male Yes Thur Lunch 2 28.4400

124 12.48 2.52 Female No Thur Lunch 2 12.4800

9 14.78 3.23 Male No Sun Dinner 2 14.7800


Py

101 15.38 3.00 Female


Py Yes Fri Dinner 2 15.3800

对比total_bill 和 fill_total_bill 发现 Male 和 Female 的填充值不同

transform练习


weight_loss数据集,找到减肥比赛赢家

# 加载数据
weight_loss = pd.read_csv('data/weight_loss.csv')


#查看数据
weight_loss

显示结果:




Name Month Week Weight

0 Bob Jan Week 1 291

1 Amy Jan Week 1 197

2 Bob Jan Week 2 288

3 Amy Jan Week 2 189

4 Bob Jan Week 3 283

5 Amy Jan Week 3 189

6 Bob Jan Week 4 283

7 Amy Jan Week 4 190

8 Bob Feb Week 1 283

9 Amy Feb Week 1 190

10 Bob Feb Week 2 275

11 Amy Feb Week 2 184

12 Bob Feb Week 3 268


on

on
13 Amy Feb Week 3 177

14 Bob Feb Week 4 268

15 Amy Feb Week 4 173

16 Bob Mar Week 1 268


th

th
17 Amy Mar Week 1 173

18 Bob Mar Week 2 271


Py

19 Amy
PyMar Week 2 173

20 Bob Mar Week 3 265

21 Amy Mar Week 3 170



22 Bob Mar Week 4 261

23 Amy Mar Week 4 170

24 Bob Apr Week 1 261



25 Amy Apr Week 1 170

26 Bob Apr Week 2 258


27 Amy Apr Week 2


程 164

28 Bob Apr Week 3 253

29 Amy Apr Week 3 164



30 Bob Apr Week 4 250

31 Amy Apr Week 4 161


Bob,Amy两个人的减肥记录,从1月到4月

# 只查看1月份数据 query 类似SQL的where条件


weight_loss.query('Month == "Jan"')

显示结果:

Name Month Week Weight

0 Bob Jan Week 1 291

1 Amy Jan Week 1 197

2 Bob Jan Week 2 288

3 Amy Jan Week 2 189

4 Bob Jan Week 3 283

5 Amy Jan Week 3 189

6 Bob Jan Week 4 283

7 Amy Jan Week 4 190

定义函数计算每周减肥比例 并测试
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

计算每周减肥比例

pcnt_loss = weight_loss.groupby(['Name', 'Month'])['Weight'].transform(find_perc_loss)


pcnt_loss.head(8)

显示结果:

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

weight_loss['Perc Weight Loss'] = pcnt_loss.round(3)


# 查找每个月最后一周的数据 用来比较减肥效果
Py

week4 = weight_loss.query('Week == "Week 4"')


week4
Py
显示结果:


Name Month Week Weight Perc Weight Loss

6 Bob Jan Week 4 283 0.027

7 Amy Jan Week 4 190 0.036



14 Bob Feb Week 4 268 0.053

15 Amy Feb Week 4 173 0.089


22 Bob Mar Week 4 261


程 0.026

23 Amy Mar Week 4 170 0.017

30 Bob Apr Week 4 250 0.042



31 Amy Apr Week 4 161 0.053

在第四周数据基础上,找到 Bob 和 Amy的减肥数据


week4_Bob = week4.query('Name == "Bob"')[['Month','Perc Weight Loss']]


week4_Bob

显示结果:

Month Perc Weight Loss

6 Jan 0.027

14 Feb 0.053

22 Mar 0.026

30 Apr 0.042

week4_Amy = week4.query('Name == "Amy"')[['Month','Perc Weight Loss']]


week4_Amy

显示结果:
Month Perc Weight Loss

7 Jan 0.036

15 Feb 0.089

23 Mar 0.017

31 Apr 0.053

比较Bob 和 Amy的减肥效果, Amy的减肥效果更明显

week4_Bob.set_index('Month')-week4_Amy.set_index('Month')

显示结果:

Month Perc Weight Loss

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人的数据比较少,考虑将这部分数据过滤掉

tips_filtered = tips.groupby('size').filter(lambda x: x['size'].count()>30)


tips_filtered


显示结果:

total_bill tip sex smoker day time size



0 16.99 1.01 Female No Sun Dinner 2

1 10.34 1.66 Male No Sun Dinner 3


2 21.01 3.50 Male No Sun Dinner 3

3 23.68 3.31 Male No Sun Dinner 2

4 24.59 3.61 Female No Sun Dinner 4

... ... ... ... ... ... ... ...

239 29.03 5.92 Male No Sat Dinner 3

240 27.18 2.00 Female Yes Sat Dinner 2

241 22.67 2.00 Male Yes Sat Dinner 2

242 17.82 1.75 Male No Sat Dinner 2

243 18.78 3.00 Female No Thur Dinner 2

查看结果

tips_filtered['size'].value_counts()

显示结果:
2 156
3 38
4 37
Name: size, dtype: int64

4 DataFrameGroupBy对象

4.1 分组
准备数据

tips_10 = pd.read_csv('data/tips.csv').sample(10,random_state = 42)


tips_10

显示结果:

total_bill tip sex smoker day time size

24 19.82 3.18 Male No Sat Dinner 2

6 8.77 2.00 Male No Sun Dinner 2

153 24.55 2.00 Male No Sun Dinner 4


on

on
211 25.89 5.16 Male Yes Sat Dinner 4

198 13.00 2.00 Female Yes Thur Lunch 2

176 17.89 2.00 Male Yes Sun Dinner 2

192 28.44 2.56 Male Yes Thur Lunch 2


th

th
124 12.48 2.52 Female No Thur Lunch 2

9 14.78 3.23 Male No Sun Dinner 2


Py

101 15.38 3.00 Female


Py Yes Fri Dinner 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 total_bill tip size

Female 13.62 2.506667 2.000000

Male 20.02 2.875714 2.571429

上面结果直接计算了按sex分组后,所有列的平均值,但只返回了数值列的结果,非数值列不会计算平均值
通过get_group选择分组

female = grouped.get_group('Female')
female

显示结果:
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

4.2 遍历分组
通过groupby对象,可以遍历所有分组,相比于在groupby之后使用aggregate、transform和filter,有时候使用for循环解决问题更简

for sex_group in grouped:


print(sex_group)

显示结果:

('Female', 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)
('Male', total_bill tip sex smoker day time size
24 19.82 3.18 Male No Sat Dinner 2
6 8.77 2.00 Male No Sun Dinner 2
on

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]

e:\python\data\lib\site-packages\pandas\core\groupby\generic.py in __getitem__(self, key)


1642 stacklevel=2,


1643 )
-> 1644 return super().__getitem__(key)
1645
1646 def _gotitem(self, key, ndim: int, subset=None):

e:\python\data\lib\site-packages\pandas\core\base.py in __getitem__(self, key)



226 else:
227 if key not in self.obj:
--> 228 raise KeyError(f"Column not found: {key}")
229 return self._gotitem(key, ndim=1)


230

KeyError: 'Column not found: 0'


for sex_group in grouped:


#遍历grouped对象,查看sex_group数据类型
print(type(sex_group))
# 查看元素个数
print(len(sex_group))
# 查看第一个元素
print(sex_group[0])
# 查看第一个元素数据类型
print(type(sex_group[0]))
# 查看第二个元素
print(sex_group[1])
# 查看第二个元素数据类型
print(type(sex_group[1]))
break

显示结果:
<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

显示结果:

total_bill tip size

sex time

Female Dinner 15.380000 3.000000 2.000000


on

on
Lunch 12.740000 2.260000 2.000000

Male Dinner 18.616667 2.928333 2.666667

Lunch 28.440000 2.560000 2.000000


th

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()

显示结果:

sex time total_bill tip size

0 Female Dinner 15.380000 3.000000 2.000000

1 Female Lunch 12.740000 2.260000 2.000000

2 Male Dinner 18.616667 2.928333 2.666667

3 Male Lunch 28.440000 2.560000 2.000000

也可以在分组的时候通过as_index = False参数(默认是True),效果与调用reset_index()一样

tips_10.groupby(['sex','time'],as_index = False).mean()

显示结果:

sex time total_bill tip size

0 Female Dinner 15.380000 3.000000 2.000000

1 Female Lunch 12.740000 2.260000 2.000000

2 Male Dinner 18.616667 2.928333 2.666667

3 Male Lunch 28.440000 2.560000 2.000000


小结
分组是数据分析中常见的操作,有助于从不同角度观察数据
分组之后可以得到DataFrameGroupby对象,该对象可以进行聚合、转换、过滤操作
分组不但可以对单个字段进行分组,也可以对多个字段进行分组,多个字段分组之后可以得到MultiIndex数据,可以通过reset_index
方法将数据变成普通的DataFrame

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

from datetime import datetime


Py
custom_info.loc[:,'注册年月'] = custom_info['注册时间'].apply(lambda x : x.strftime('%Y-%m'))
custom_info[['会员卡号','会员等级','会员来源','注册时间','注册年月']].head()

显示结果:


会员卡号 会员等级 会员来源 注册时间 注册年月

0 BL6099033963550303769 黄金会员 线下扫码 2019-03-31 23:55:03.977 2019-03



1 BL6099033963450303763 黄金会员 线下扫码 2019-03-31 23:45:03.005 2019-03

2 BL6099033963464003767 白银会员 电商入口 2019-03-31 23:42:40.073 2019-03


3 BL6099033963460503766 黄金会员 线下扫码



2019-03-31 23:42:05.516 2019-03

4 BL6099033963660603765 白银会员 电商入口 2019-03-31 23:26:02.402 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:聚合函数

custom_info.pivot_table(index = '注册年月',values = '会员卡号',aggfunc = 'count')

显示结果:
会员卡号

注册年月

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 对某一列 做累积求和 1 1+2 1+2+3 1+2+3+4 ...



#通过cumsum 对月增量做累积求和
month_count.loc[:,'存量'] = month_count['月增量'].cumsum()
month_count


显示结果:




月增量 存量

注册年月

2017-08 392910 392910

2017-09 760 393670

2017-10 996 394666

2017-11 1710 396376

2017-12 4165 400541

2018-01 15531 416072

2018-02 13798 429870

2018-03 49320 479190

2018-04 71699 550889

2018-05 27009 577898

2018-06 17718 595616

2018-07 8483 604099


on

on
2018-08 109674 713773

2018-09 147585 861358

2018-10 14654 876012

2018-11 9912 885924


th

th
2018-12 6460 892384

2019-01 9795 902179


Py

2019-02 12163
Py 914342

2019-03 38372 952714

可视化,需要去除第一个月数据,第一个月数据是之前所有会员数量的累积(数据质量问题)


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

显示结果:

会员卡号

注册年月 会员等级

2017-08 白银会员 376648

钻石会员 185

铂金会员 387

黄金会员 15690

2017-09 白银会员 530

... ... ...


on

on
2019-02 黄金会员 8140

2019-03 白银会员 10580

钻石会员 3
th

th
铂金会员 37

黄金会员 27752
Py

80 rows × 1 columns
Py
分组之后得到的是multiIndex类型的索引,将multiIndex索引变成普通索引

#使用reset_index()


month_degree_count.reset_index()

显示结果:


注册年月 会员等级 会员卡号

0 2017-08 白银会员 376648

1 2017-08 钻石会员 185


2 2017-08 铂金会员 387



3 2017-08 黄金会员 15690

4 2017-09 白银会员 530


... ... ... ...

75 2019-02 黄金会员 8140


76 2019-03 白银会员 10580

77 2019-03 钻石会员 3

78 2019-03 铂金会员 37

79 2019-03 黄金会员 27752

80 rows × 3 columns

#使用unstack()
month_degree_count.unstack()

显示结果:
会员卡号

会员等级 白银会员 钻石会员 铂金会员 黄金会员

注册年月

2017-08 376648 185 387 15690

2017-09 530 3 10 217

2017-10 603 13 17 363

2017-11 1311 19 20 360

2017-12 3512 15 24 614

2018-01 11244 24 64 4199

2018-02 9937 9 34 3818

2018-03 41546 57 94 7623

2018-04 62613 48 83 8955

2018-05 19317 19 56 7617

2018-06 11292 7 23 6396


on

on
2018-07 3932 8 28 4515

2018-08 95584 27 65 13998

2018-09 133090 20 63 14412

2018-10 9093 15 34 5512


th

th
2018-11 6313 4 29 3566

2018-12 2808 3 29 3620


Py

2019-01 3661
Py 5 9 6120

2019-02 4001 5 17 8140

2019-03 10580 3 37 27752



使用透视表实现

member_rating = custom_info.pivot_table(index = '注册年月',columns='会员等级',values='会员卡号',aggfunc =


'count')


member_rating

显示结果:




会员等级 白银会员 钻石会员 铂金会员 黄金会员

注册年月

2017-08 376648 185 387 15690

2017-09 530 3 10 217

2017-10 603 13 17 363

2017-11 1311 19 20 360

2017-12 3512 15 24 614

2018-01 11244 24 64 4199

2018-02 9937 9 34 3818

2018-03 41546 57 94 7623

2018-04 62613 48 83 8955

2018-05 19317 19 56 7617

2018-06 11292 7 23 6396

2018-07 3932 8 28 4515


on

on
2018-08 95584 27 65 13998

2018-09 133090 20 63 14412

2018-10 9093 15 34 5512

2018-11 6313 4 29 3566


th

th
2018-12 2808 3 29 3620

2019-01 3661 5 9 6120


Py

2019-02 4001
Py 5 17 8140

2019-03 10580 3 37 27752



#去掉首月数据
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

显示结果:

会员等级 白银会员 钻石会员 铂金会员 黄金会员 总计 白银会员占比 黄金会员占比

注册年月

2017-09 530 3 10 217 760.0 0.697368 0.285526

2017-10 603 13 17 363 996.0 0.605422 0.364458

2017-11 1311 19 20 360 1710.0 0.766667 0.210526

2017-12 3512 15 24 614 4165.0 0.843217 0.147419

2018-01 11244 24 64 4199 15531.0 0.723971 0.270363

2018-02 9937 9 34 3818 13798.0 0.720177 0.276707

2018-03 41546 57 94 7623 49320.0 0.842376 0.154562


on

on
2018-04 62613 48 83 8955 71699.0 0.873276 0.124897

2018-05 19317 19 56 7617 27009.0 0.715206 0.282017

2018-06 11292 7 23 6396 17718.0 0.637318 0.360989


th

th
2018-07 3932 8 28 4515 8483.0 0.463515 0.532241

2018-08 95584 27 65 13998 109674.0 0.871528 0.127633

2018-09 133090 20 63 14412 147585.0 0.901785 0.097652


Py

2018-10 9093 15 34
Py 5512 14654.0 0.620513 0.376143

2018-11 6313 4 29 3566 9912.0 0.636905 0.359766

2018-12 2808 3 29 3620 6460.0 0.434675 0.560372



2019-01 3661 5 9 6120 9795.0 0.373762 0.624809

2019-02 4001 5 17 8140 12163.0 0.328948 0.669243



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

钻石会员 489 0.000513


Py

铂金会员 1123
Py 0.001179

黄金会员 143487 0.150609

报表可视化


# 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

2017-12 510 3655

2018-01 11739 3792

2018-02 10665 3133

2018-03 6652 42668

2018-04 3396 68303

2018-05 3560 23449

2018-06 3368 14350

2018-07 1290 7193


on

on
2018-08 9166 100508

2018-09 10943 136642

2018-10 3281 11373


th

th
2018-11 2350 7562

2018-12 1108 5352

2019-01 946 8849


Py

2019-02 844
Py 11319

2019-03 1383 36989



# 透视表实现
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

#统计不同地区的会员数量 注意只统计线下,不统计电商渠道 GBL6D01为电商


district = custom_info1[custom_info1['地区编码']!='GBL6D01'].groupby('地区编码')[['会员卡号']].count()
#修改列名
district.columns = ['会员数量']
district

显示结果:
会员数量

地区编码

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

GBL6070 147804 114



GBL6080 66750 70

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()

显示结果:
会员数量 店铺数 每店平均会员数 总平均会员数

地区编码

GBL6060 74447 31 2402.0 1173.60719

GBL6030 112114 78 1437.0 1173.60719

GBL6040 63426 47 1349.0 1173.60719

GBL6050 50474 38 1328.0 1173.60719

GBL6070 147804 114 1297.0 1173.60719

数据可视化

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()

显示结果:

卡号 订单日期 订单号 订单类型 店铺代码 款号 尺码 消费数量 消费金额 当前积分


0 HS340766JAF6 2018-11-30 ODLOX6BXX8X2BXBBBBX 下单 DPX60X BLA267Q3X13AQM 230 1 1200.0 800

1 BL6093039999465603590 2018-11-30 ODODOXF77X8X2BXBBBB2 下单 DPX377 BLA2651QX14AQC 240 1 1332.0 2531

2 BL6093909593939600407 2018-11-30 ROX8XXFBBBB6BB 退单 DPS00X TMA27727X5QAQM 240 -1 -112.5 328

3 BL6093036096030709394 2018-11-30 ROX8XXFBBBB6X7 退单 DPS00X TMA67621X5QBQTM 230 -1 -60.0 1038

4 BL6093993066943700650 2018-11-30 ODLOX6BFX8XXFBBBBBX 下单 DPX603 BLA26663X52AQTM 235 1 1200.0 800

all_orders.head()

显示结果:

年月 地区代码 店铺代码 全部订单数 会员订单数

0 201801 GBL6030 DPX077 130 7.0

1 201801 GBL6030 DPX078 277 19.0

2 201801 GBL6030 DPX079 163 1.0

3 201801 GBL6030 DPX08X 154 5.0

4 201801 GBL6030 DPX082 276 14.0

为会员消费报表添加年月列
#添加年月 这里年月要转换成整数,因为等会后面要链接的字段是整数
custom_consume.loc[:,'年月']=pd.to_datetime(custom_consume['订单日期']).apply(lambda
x:datetime.strftime(x,'%Y%m')).astype(np.int)
custom_consume.head()

显示结果:

卡号 订单日期 订单号 订单类型 店铺代码 款号 尺码 消费数量 消费金额 当前积分 年月

0 HS340766JAF6 2018-11-30 ODLOX6BXX8X2BXBBBBX 下单 DPX60X BLA267Q3X13AQM 230 1 1200.0 800 201811

1 BL6093039999465603590 2018-11-30 ODODOXF77X8X2BXBBBB2 下单 DPX377 BLA2651QX14AQC 240 1 1332.0 2531 201811

2 BL6093909593939600407 2018-11-30 ROX8XXFBBBB6BB 退单 DPS00X TMA27727X5QAQM 240 -1 -112.5 328 201811

3 BL6093036096030709394 2018-11-30 ROX8XXFBBBB6X7 退单 DPS00X TMA67621X5QBQTM 230 -1 -60.0 1038 201811

4 BL6093993066943700650 2018-11-30 ODLOX6BFX8XXFBBBBBX 下单 DPX603 BLA26663X52AQTM 235 1 1200.0 800 201811

为会员消费报表添加地区编码

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

GBL6010 127 74 145 113 70 35 45 82 56 61 51 43 253 1155

GBL6020 15 31 15 5 499 593 90 980 588 80 8 55 291 3250

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

GBL6080 10 9 16 4 91 53 23 1221 1120 121 47 49 670 3434

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

on
用到的数据:

会员消费报表.xlsx 会员消费记录
门店信息表.xlsx 建立门店地区对应关系
分析连带率的作用
通过连带率分析可以反映出人、货、场几个角度的业务问题
th

th
代码实现

统计订单的数量:需要对"订单号"去重,并且只要"下单"的数据,"退单"的不要
Py

order_data=custom_consume.query(" 订单类型=='下单' & 地区编码!='GBL6D01'")


Py
#去重 统计订单量需要去重 后面统计消费数量和消费金额不需要去重
order_count=order_data[['年月','地区编码','订单号']].drop_duplicates()
order_count.pivot_table(index = '地区编码',columns='年月',values='订单号',aggfunc='count')


显示结果:

年月 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

GBL6020 15 31 15 5 479 560 74 919 540 79 8 40 192

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

GBL6080 11 9 14 4 70 40 17 960 845 95 40 38 562

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

地区编码

GBL6010 133 81 155 122 79 37 45 85 61 61 52 44 261

GBL6020 15 31 15 5 517 623 102 1012 613 92 9 56 292

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

... ... ... ...


109166 GBL6D01 SB069030JAF6 2

109167 GBL6D01 SB090405JA7A 1

109168 GBL6D01 SB096777JA93 4

109169 GBL6D01 SB099053JAB3 2

109170 GBL6D01 SB903649JADE 1

109171 rows × 3 columns

判断是否复购

consume_count['是否复购']=consume_count['消费次数']>1
consume_count

显示结果:
地区编码 卡号 消费次数 是否复购

0 GBL6010 BL6093030369930903555 1 False

1 GBL6010 BL6093030394336509657 1 False

2 GBL6010 BL6093030394665709666 1 False

3 GBL6010 BL6093030394669709994 1 False

4 GBL6010 BL6093030396343606006 1 False

... ... ... ... ...

109166 GBL6D01 SB069030JAF6 2 True

109167 GBL6D01 SB090405JA7A 1 False

109168 GBL6D01 SB096777JA93 4 True

109169 GBL6D01 SB099053JAB3 2 True

109170 GBL6D01 SB903649JADE 1 False

109171 rows × 4 columns

计算复购率并定义函数
on

on
统计每个地区的购买人数和复购人数

consume_count.pivot_table(index = ['地区编码'],values=['消费次数','是否复购'],aggfunc={'消费次数':'count','是否
复购':'sum'})
depart_data.columns=['复购人数','购买人数']
th

th
depart_data

显示结果:

复购人数 购买人数
Py

地区编码
Py
GBL6010 64 791

GBL6020 126 2608



GBL6030 2606 26395

GBL6040 454 3918



GBL6050 1467 11129

GBL6060 440 4250

GBL6070 1498 7991



GBL6080 127 2335

GBL6090 582 3518



GBL6100 528 5546

GBL6110 2120 15238

GBL6120 239 1601


GBL6130 460 5151

GBL6140 839 7960

GBL6D01 1651 10740

计算复购率

depart_data.loc[:,'复购率']=depart_data['复购人数']/depart_data['购买人数']
depart_data

显示结果:
复购人数 购买人数 复购率

地区编码

GBL6010 791 64 12.359375

GBL6020 2608 126 20.698413

GBL6030 26395 2606 10.128550

GBL6040 3918 454 8.629956

GBL6050 11129 1467 7.586230

GBL6060 4250 440 9.659091

GBL6070 7991 1498 5.334446

GBL6080 2335 127 18.385827

GBL6090 3518 582 6.044674

GBL6100 5546 528 10.503788

GBL6110 15238 2120 7.187736

GBL6120 1601 239 6.698745


on

on
GBL6130 5151 460 11.197826

GBL6140 7960 839 9.487485

GBL6D01 10740 1651 6.505148


th

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复购率

地区编码

GBL6010 55 611 0.090016

GBL6020 107 2461 0.043478

GBL6030 2297 24108 0.095280

GBL6040 423 3669 0.115290

GBL6050 1313 10454 0.125598

GBL6060 429 4171 0.102853

GBL6070 1379 7601 0.181423

GBL6080 98 1849 0.053002

GBL6090 534 3388 0.157615

GBL6100 451 4768 0.094589

GBL6110 1829 13932 0.131281

GBL6120 214 1543 0.138691


on

on
GBL6130 415 4568 0.090849

GBL6140 766 7296 0.104989

GBL6D01 1524 10356 0.147161


th

th
计算2018年02月~2019年01月的复购率

reorder_2019=stats_reorder(201802,201901,'2018.02-2019.01')
reorder_2019
Py

显示结果:
Py
复购人数 购买人数 2018.02-2019.01复购率


地区编码

GBL6010 55 702 0.078348

GBL6020 125 2600 0.048077



GBL6030 2410 25161 0.095783

GBL6040 423 3737 0.113192

GBL6050 1402 10783 0.130019



GBL6060 391 3971 0.098464

GBL6070 1382 7585 0.182202



GBL6080 127 2326 0.054600

GBL6090 559 3428 0.163069

GBL6100 506 5363 0.094350


GBL6110 2040 14836 0.137503

GBL6120 227 1534 0.147979

GBL6130 428 4915 0.087080

GBL6140 789 7641 0.103259

GBL6D01 1651 10740 0.153724

计算复购率环比

#合并数据
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复购率 同比

地区编码

GBL6010 9.00% 7.83% -1.17%

GBL6020 4.35% 4.81% 0.46%

GBL6030 9.53% 9.58% 0.05%

GBL6040 11.53% 11.32% -0.21%

GBL6050 12.56% 13.00% 0.44%

GBL6060 10.29% 9.85% -0.44%

GBL6070 18.14% 18.22% 0.08%

GBL6080 5.30% 5.46% 0.16%

GBL6090 15.76% 16.31% 0.55%

GBL6100 9.46% 9.44% -0.02%

GBL6110 13.13% 13.75% 0.62%

GBL6120 13.87% 14.80% 0.93%


on

on
GBL6130 9.08% 8.71% -0.38%

GBL6140 10.50% 10.33% -0.17%

GBL6D01 14.72% 15.37% 0.66%


th

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.datetime(2020, 6, 17, 19, 47, 56, 965416)


还可以手动创建datetime

t1 = datetime.now()
t2 = datetime(2020,1,1)
diff = t1-t2
print(diff)

显示结果:

168 days, 20:16:50.438044

查看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 Day Cases_Guinea Cases_Liberia Cases_SierraLeone

0 1/5/2015 289 2776.0 NaN 10030.0

1 1/4/2015 288 2775.0 NaN 9780.0

2 1/3/2015 287 2769.0 8166.0 9722.0

3 1/2/2015 286 NaN 8157.0 NaN

4 12/31/2014 284 2730.0 8115.0 9633.0

从数据中看出 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'>

RangeIndex: 122 entries, 0 to 121


Data columns (total 19 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 122 non-null object
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
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 date_dt 122 non-null datetime64[ns]
dtypes: datetime64[ns](1), float64(16), int64(1), object(1)
memory usage: 18.2+ KB
如果数据中包含日期时间数据,可以在加载的时候,通过parse_dates参数指定

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()

显示结果:

Date year month day

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

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
18 year 122 non-null int64

19 month 122 non-null int64


20 day 122 non-null int64
dtypes: datetime64[ns](1), float64(16), int64(4)
memory usage: 20.1 KB

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

0 2015-01-05 289 289 days

1 2015-01-04 288 288 days

2 2015-01-03 287 287 days

3 2015-01-02 286 286 days

4 2014-12-31 284 284 days

ebola[['Date','Day','outbreak_d']].tail()

显示结果:

Date Day outbreak_d

117 2014-03-27 5 5 days

118 2014-03-26 4 4 days

119 2014-03-25 3 3 days

120 2014-03-24 2 2 days


on

on
121 2014-03-22 0 0 days

执行这种日期运算,会得到一个timedelta对象

ebola.info()
th

th
显示结果:

<class 'pandas.core.frame.DataFrame'>
Py

RangeIndex: 122 entries, 0 to 121


Data columns (total 22 columns):
Py
# 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
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

21 outbreak_d 122 non-null timedelta64[ns]


dtypes: datetime64[ns](1), float64(16), int64(4), timedelta64[ns](1)
memory usage: 21.1 KB

案例

# 加载数据
banks = pd.read_csv('data/banklist.csv')
banks.head()

显示结果:
Closing Updated
Bank Name City ST CERT Acquiring Institution
Date Date

United Fidelity Bank, 26-


0 Fayette County Bank Saint Elmo IL 1802 26-Jul-17
fsb May-17

Guaranty Bank, (d/b/a BestBank in First-Citizens Bank & 5-May-


1 Milwaukee WI 30003 26-Jul-17
Georgia & Mi... Trust Company 17

28-Apr-
2 First NBC Bank New Orleans LA 58302 Whitney Bank 26-Jul-17
17

Cottonwood 3-Mar- 18-May-


3 Proficio Bank UT 35495 Cache Valley Bank
Heights 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

dtypes: datetime64[ns](2), int64(1), object(4)


memory usage: 30.4+ KB
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()

可以使用绘图函数绘制结果

import matplotlib.pyplot as plt


%matplotlib inline
fig,ax = plt.subplots()
closing_year.plot()

显示结果:
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

0 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000

1 2015-08-20 254.559998 241.899994 252.059998 242.179993 4905800 242.179993


2 2015-08-21 243.800003 230.509995 236.000000 230.770004



6590200 230.770004

3 2015-08-24 231.399994 195.000000 202.789993 218.869995 9581600 218.869995

4 2015-08-25 230.899994 219.119995 230.520004 220.029999 4327300 220.029999

... ... ... ... ... ... ... ...



1210 2020-06-10 1027.479980 982.500000 991.880005 1025.050049 18563400 1025.050049

1211 2020-06-11 1018.960022 972.000000 990.200012 972.840027 15916500 972.840027

1212 2020-06-12 987.979980 912.599976 980.000000 935.280029 16730200 935.280029


1213 2020-06-15 998.840027 908.500000 917.789978 990.900024 15697200 990.900024

1214 2020-06-16 1012.880005 962.390015 1011.849976 982.130005 14051100 982.130005

1215 rows × 7 columns

可以看出,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)]

显示结果:

Date High Low Open Close Volume Adj Close

0 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000

1 2015-08-20 254.559998 241.899994 252.059998 242.179993 4905800 242.179993

2 2015-08-21 243.800003 230.509995 236.000000 230.770004 6590200 230.770004

3 2015-08-24 231.399994 195.000000 202.789993 218.869995 9581600 218.869995

4 2015-08-25 230.899994 219.119995 230.520004 220.029999 4327300 220.029999

5 2015-08-26 228.000000 215.509995 227.929993 224.839996 4963000 224.839996

6 2015-08-27 244.750000 230.809998 231.000000 242.990005 7656000 242.990005

7 2015-08-28 251.449997 241.570007 241.860001 248.479996 5513700 248.479996

8 2015-08-31 254.949997 245.509995 245.619995 249.059998 4700200 249.059998

DatetimeIndex对象
on

on
在处理包含datetime的数据时,经常需要把datetime对象设置成DateFrame的索引

#首先把Date列指定为索引
tesla.index = tesla['Date']
tesla.index
th

th
显示结果:

DatetimeIndex(['2015-08-19', '2015-08-20', '2015-08-21', '2015-08-24',


Py

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 High Low Open Close Volume Adj Close


Date

2015-08-19 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000

2015-08-20 2015-08-20 254.559998 241.899994 252.059998 242.179993 4905800 242.179993

2015-08-21 2015-08-21 243.800003 230.509995 236.000000 230.770004 6590200 230.770004



2015-08-24 2015-08-24 231.399994 195.000000 202.789993 218.869995 9581600 218.869995

2015-08-25 2015-08-25 230.899994 219.119995 230.520004 220.029999 4327300 220.029999

把索引设置为日期对象后,可以直接使用日期来获取某些数据

tesla['2016'].iloc[:5]

显示结果:

Date High Low Open Close Volume Adj Close

Date

2016-01-04 2016-01-04 231.380005 219.000000 230.720001 223.410004 6827100 223.410004

2016-01-05 2016-01-05 226.889999 220.000000 226.360001 223.429993 3186800 223.429993

2016-01-06 2016-01-06 220.050003 215.979996 220.000000 219.039993 3779100 219.039993

2016-01-07 2016-01-07 218.440002 213.669998 214.190002 215.649994 3554300 215.649994

2016-01-08 2016-01-08 220.440002 210.770004 217.860001 211.000000 3628100 211.000000

也可以根据年份和月份获取数据

tesla['2016-06'].iloc[:5]

显示结果:
Date High Low Open Close Volume Adj Close

Date

2016-06-01 2016-06-01 222.399994 216.889999 221.479996 219.559998 2982700 219.559998

2016-06-02 2016-06-02 219.910004 217.110001 219.589996 218.960007 2032800 218.960007

2016-06-03 2016-06-03 221.940002 218.009995 220.000000 218.990005 2229000 218.990005

2016-06-06 2016-06-06 220.899994 215.449997 218.000000 220.679993 2249500 220.679993

2016-06-07 2016-06-07 234.440002 221.520004 222.240005 232.339996 6213600 232.339996

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

4 Close 1215 non-null float64



5 Volume 1215 non-null int64
6 Adj Close 1215 non-null float64
7 ref_date 1215 non-null timedelta64[ns]
dtypes: datetime64[ns](1), float64(5), int64(1), timedelta64[ns](1)


memory usage: 85.4 KB

tesla.head()

显示结果:

Date High Low Open Close Volume Adj Close ref_date

ref_date

0 days 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000 0 days

1 days 2015-08-20 254.559998 241.899994 252.059998 242.179993 4905800 242.179993 1 days

2 days 2015-08-21 243.800003 230.509995 236.000000 230.770004 6590200 230.770004 2 days

5 days 2015-08-24 231.399994 195.000000 202.789993 218.869995 9581600 218.869995 5 days

6 days 2015-08-25 230.899994 219.119995 230.520004 220.029999 4327300 220.029999 6 days

可以基于ref_date来选择数据

tesla['0 days':'4 days']

显示结果:

Date High Low Open Close Volume Adj Close ref_date

ref_date

0 days 2015-08-19 260.649994 255.020004 260.329987 255.250000 3604300 255.250000 0 days

1 days 2015-08-20 254.559998 241.899994 252.059998 242.179993 4905800 242.179993 1 days

2 days 2015-08-21 243.800003 230.509995 236.000000 230.770004 6590200 230.770004 2 days


6 日期范围
包含日期的数据集中,并非每一个都包含频率。比如在Ebola数据集中,日期并没有规律

ebola.iloc[:,:5]

显示结果:

Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone

0 2015-01-05 289 2776.0 NaN 10030.0

1 2015-01-04 288 2775.0 NaN 9780.0

2 2015-01-03 287 2769.0 8166.0 9722.0

3 2015-01-02 286 NaN 8157.0 NaN

4 2014-12-31 284 2730.0 8115.0 9633.0

... ... ... ... ... ...

117 2014-03-27 5 103.0 8.0 6.0

118 2014-03-26 4 86.0 NaN NaN

119 2014-03-25 3 86.0 NaN NaN


on

on
120 2014-03-24 2 86.0 NaN NaN

121 2014-03-22 0 49.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

2014-12-31 2014-12-31 284.0 2730.0 8115.0 9633.0



2015-01-01 NaT NaN NaN NaN NaN

2015-01-02 2015-01-02 286.0 NaN 8157.0 NaN

2015-01-03 2015-01-03 287.0 2769.0 8166.0 9722.0


2015-01-04 2015-01-04 288.0 2775.0 NaN 9780.0

2015-01-05 2015-01-05 289.0 2776.0 NaN 10030.0

使用date_range函数创建日期序列时,可以传入一个参数freq,默认情况下freq取值为D,表示日期范围内的值是逐日递增的。

# 2020年1月1日这周所有的工作日
pd.date_range('2020-01-01','2020-01-07',freq='B')

显示结果:

DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',


'2020-01-07'],
dtype='datetime64[ns]', 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

BAS, BYS 年初工作日


Py
BH 工作时间

H 小时


T, min 分钟

S 秒

L, ms 毫秒


U, us microseconds

N 纳秒


在freq传入参数的基础上,可以做一些调整

# 隔一个工作日取一个工作日


pd.date_range('2020-01-01','2020-01-07',freq='2B')

显示结果:

DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-07'], dtype='datetime64[ns]', freq='2B')

freq传入的参数可以传入多个

#2020年每个月的第一个星期四
pd.date_range('2020-01-01','2020-12-31',freq='WOM-1THU')

显示结果:

DatetimeIndex(['2020-01-02', '2020-02-06', '2020-03-05', '2020-04-02',


'2020-05-07', '2020-06-04', '2020-07-02', '2020-08-06',
'2020-09-03', '2020-10-01', '2020-11-05', '2020-12-03'],
dtype='datetime64[ns]', freq='WOM-1THU')

#每个月的第三个星期五
pd.date_range('2020-01-01','2020-12-31',freq='WOM-3FRI')

显示结果:

DatetimeIndex(['2020-01-17', '2020-02-21', '2020-03-20', '2020-04-17',


'2020-05-15', '2020-06-19', '2020-07-17', '2020-08-21',
'2020-09-18', '2020-10-16', '2020-11-20', '2020-12-18'],
dtype='datetime64[ns]', freq='WOM-3FRI')
7 datetime类型案例
加载丹佛市犯罪记录数据集

crime = pd.read_csv('data/crime.csv',parse_dates=['REPORTED_DATE'])
crime

显示结果:

OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID REPORTED_DATE GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC

0 traffic-accident-dui-duid traffic-accident 2014-06-29 02:01:00 -105.000149 39.745753 cbd 0 1

1 vehicular-eluding-no-chase all-other-crimes 2014-06-29 01:54:00 -104.884660 39.738702 east-colfax 1 0

2 disturbing-the-peace public-disorder 2014-06-29 02:00:00 -105.020719 39.706674 athmar-park 1 0

3 curfew public-disorder 2014-06-29 02:18:00 -105.001552 39.769505 sunnyside 1 0

4 aggravated-assault aggravated-assault 2014-06-29 04:17:00 -105.018557 39.679229 college-view-south-platte 1 0

... ... ... ... ... ... ... ... ...

460906 burglary-business-by-force burglary 2017-09-13 05:48:00 -105.033840 39.762365 west-highland 1 0

460907 weapon-unlawful-discharge-of all-other-crimes 2017-09-12 20:37:00 -105.040313 39.721264 barnum-west 1 0

460908 traf-habitual-offender all-other-crimes 2017-09-12 16:32:00 -104.847024 39.779596 montbello 1 0

460909 criminal-mischief-other public-disorder 2017-09-12 13:04:00 -104.949182 39.756353 skyland 1 0

460910 theft-other larceny 2017-09-12 09:30:00 -104.985739 39.735045 capitol-hill 1 0

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

2016-05-12 18:40:00 liquor-possession drug-alcohol -104.995692 39.747875 cbd 1 0

2016-05-12 22:26:00 traffic-accident traffic-accident -104.880037 39.777037 stapleton 0 1

2016-05-12 20:35:00 theft-bicycle larceny -104.929350 39.763797 northeast-park-hill 1 0


2016-05-12 09:39:00 theft-of-motor-vehicle auto-theft -104.941233 39.775510 elyria-swansea 1 0

... ... ... ... ... ... ... ...

2016-05-12 17:55:00 public-peace-other public-disorder -105.027747 39.700029 westwood 1 0

2016-05-12 19:24:00 threats-to-injure public-disorder -104.947118 39.763777 clayton 1 0

2016-05-12 22:28:00 sex-aslt-rape sexual-assault NaN NaN harvey-park-south 1 0

2016-05-12 15:59:00 menacing-felony-w-weap aggravated-assault -104.935172 39.723703 hilltop 1 0

2016-05-12 16:39:00 assault-dv other-crimes-against-persons -104.974700 39.740555 north-capitol-hill 1 0

243 rows × 7 columns

查看某一段时间的犯罪记录

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

2015-03-04 00:11:00 assault-dv other-crimes-against-persons -105.021966 39.770883 sunnyside 1 0

2015-03-04 00:19:00 assault-dv other-crimes-against-persons -104.978988 39.748799 five-points 1 0

2015-03-04 00:27:00 theft-of-services larceny -105.055082 39.790564 regis 1 0

2015-03-04 00:49:00 traffic-accident-hit-and-run traffic-accident -104.987454 39.701378 washington-park-west 0 1

2015-03-04 01:07:00 burglary-business-no-force burglary -105.010843 39.762538 highland 1 0

... ... ... ... ... ... ... ...

2016-01-01 23:15:00 traffic-accident-hit-and-run traffic-accident -104.996861 39.738612 civic-center 0 1

2016-01-01 23:16:00 traffic-accident traffic-accident -105.025088 39.707590 westwood 0 1

2016-01-01 23:40:00 robbery-business robbery -105.039236 39.726157 villa-park 1 0

2016-01-01 23:45:00 drug-cocaine-possess drug-alcohol -104.987310 39.753598 five-points 1 0

2016-01-01 23:48:00 drug-poss-paraphernalia drug-alcohol -104.986020 39.752541 five-points 1 0

75403 rows × 7 columns

时间段可以包括小时分钟

crime.loc['2015-3-4 22':'2016-1-1 23:45:00'].sort_index()

显示结果:

OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC

REPORTED_DATE

2015-03-04 22:25:00 traffic-accident-hit-and-run traffic-accident -104.973896 39.769064 five-points 0 1

2015-03-04 22:30:00 traffic-accident traffic-accident -104.906412 39.632816 hampden-south 0 1


on

on
2015-03-04 22:32:00 traffic-accident-hit-and-run traffic-accident -104.979180 39.706613 washington-park-west 0 1

2015-03-04 22:33:00 traffic-accident-hit-and-run traffic-accident -104.991655 39.740067 civic-center 0 1

2015-03-04 22:36:00 theft-unauth-use-of-ftd white-collar-crime -105.045234 39.667928 harvey-park 1 0

... ... ... ... ... ... ... ...

2016-01-01 23:07:00 traf-other all-other-crimes -104.980400 39.740144 north-capitol-hill 1 0

2016-01-01 23:15:00 traffic-accident-hit-and-run traffic-accident -104.996861 39.738612 civic-center 0 1


th

th
2016-01-01 23:16:00 traffic-accident traffic-accident -105.025088 39.707590 westwood 0 1

2016-01-01 23:40:00 robbery-business robbery -105.039236 39.726157 villa-park 1 0

2016-01-01 23:45:00 drug-cocaine-possess drug-alcohol -104.987310 39.753598 five-points 1 0

75175 rows × 7 columns


Py

查询凌晨两点到五点的报警记录
Py
crime.between_time('2:00', '5:00', include_end=False)


显示结果:

OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC

REPORTED_DATE


2014-06-29 02:01:00 traffic-accident-dui-duid traffic-accident -105.000149 39.745753 cbd 0 1

2014-06-29 02:00:00 disturbing-the-peace public-disorder -105.020719 39.706674 athmar-park 1 0

2014-06-29 02:18:00 curfew public-disorder -105.001552 39.769505 sunnyside 1 0

2014-06-29 04:17:00 aggravated-assault aggravated-assault -105.018557 39.679229 college-view-south-platte 1 0

2014-06-29 04:22:00 violation-of-restraining-order all-other-crimes -104.972447 39.739449 cheesman-park 1 0


... ... ... ... ...


程 ... ... ...

2017-08-25 04:41:00 theft-items-from-vehicle theft-from-motor-vehicle -104.880586 39.645164 hampden-south 1 0

2017-09-13 04:17:00 theft-of-motor-vehicle auto-theft -105.028694 39.708288 westwood 1 0

2017-09-13 02:21:00 assault-simple other-crimes-against-persons -104.925733 39.654184 university-hills 1 0

2017-09-13 03:21:00 traffic-accident-dui-duid traffic-accident -105.010711 39.757385 highland 0 1



2017-09-13 02:15:00 traffic-accident-hit-and-run traffic-accident -105.043950 39.787436 regis 0 1

29078 rows × 7 columns

查看发生在某个时刻的犯罪记录

crime.at_time('5:47')

显示结果:

OFFENSE_TYPE_ID OFFENSE_CATEGORY_ID GEO_LON GEO_LAT NEIGHBORHOOD_ID IS_CRIME IS_TRAFFIC

REPORTED_DATE

2013-11-26 05:47:00 criminal-mischief-other public-disorder -104.991476 39.751535 cbd 1 0

2017-04-09 05:47:00 criminal-mischief-mtr-veh public-disorder -104.959394 39.678425 university 1 0

2017-02-19 05:47:00 criminal-mischief-other public-disorder -104.986767 39.741336 north-capitol-hill 1 0

2017-02-16 05:47:00 aggravated-assault aggravated-assault -104.934029 39.732320 hale 1 0

2017-02-12 05:47:00 police-interference all-other-crimes -104.976306 39.722644 speer 1 0

... ... ... ... ... ... ... ...

2013-09-10 05:47:00 traffic-accident traffic-accident -104.986311 39.708426 washington-park-west 0 1

2013-03-14 05:47:00 theft-other larceny -105.047861 39.727237 villa-park 1 0

2012-10-08 05:47:00 theft-items-from-vehicle theft-from-motor-vehicle -105.037308 39.768336 west-highland 1 0

2013-08-21 05:47:00 theft-items-from-vehicle theft-from-motor-vehicle -105.021310 39.758076 jefferson-park 1 0

2017-08-23 05:47:00 traffic-accident-hit-and-run traffic-accident -104.931056 39.702503 washington-virginia-vale 0 1

118 rows × 7 columns

在按时间段选取数据时,可以将时间索引排序,排序之后再选取效率更高
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='丹佛犯罪情况')

显示结果:
分析每季度的犯罪和交通事故数据

crime_quarterly = crime_sort.resample('Q')['IS_CRIME', 'IS_TRAFFIC'].sum()


crime_quarterly

显示结果:

IS_CRIME IS_TRAFFIC

REPORTED_DATE

2012-03-31 7882 4726


on

on
2012-06-30 9641 5255

2012-09-30 10566 5003

2012-12-31 9197 4802


th

th
2013-03-31 8730 4442

2013-06-30 12259 4510

2013-09-30 15799 4942


Py

2013-12-31
Py 13910 4968

2014-03-31 14487 5021

2014-06-30 15833 5225



2014-09-30 17342 5734

2014-12-31 15028 5783



2015-03-31 14989 5380

2015-06-30 16924 5825

2015-09-30 17891 5988


2015-12-31 16090
程 6117

2016-03-31 16423 5590



2016-06-30 17547 5861

2016-09-30 17427 6199

2016-12-31 15984 6094


2017-03-31 16426 5587

2017-06-30 17486 6148

2017-09-30 17990 6101

所有日期都是该季度的最后一天,使用QS生成每季度的第一天

crime_sort.resample('QS')['IS_CRIME', 'IS_TRAFFIC'].sum().head()

显示结果:

IS_CRIME IS_TRAFFIC

REPORTED_DATE

2012-01-01 7882 4726

2012-04-01 9641 5255

2012-07-01 10566 5003

2012-10-01 9197 4802

2013-01-01 8730 4442


查看第二季度的数据,检验结果

crime_sort.loc['2012-4-1':'2012-6-30', ['IS_CRIME', 'IS_TRAFFIC']].sum()

显示结果:

IS_CRIME 9641
IS_TRAFFIC 5255
dtype: int64

结果可视化

plot_kwargs = dict(figsize=(16,4), color=['black', 'blue'], title='丹佛犯罪和交通事故数据')


crime_quarterly.plot(**plot_kwargs)

显示结果: 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 (追求可视化效果,推荐使用)

ECharts,是百度开源,使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器


(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数
据可视化图表


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轴坐标

plt.figure(figsize=(15,3)) #figure 创建画布 figsize指定画布大小


plt.plot(x, y) #plot 绘图
plt.xlim(0, 10) #xlim 设置x轴坐标的范围
plt.ylim(-3, 8) #ylim 设置y轴坐标的范围
plt.xlabel('X Axis',size=20) # 设置x轴标签 size字体大小
plt.ylabel('Y axis') # 设置y轴标签
plt.title('Line Plot',size=30) # 设置标题内容, size 字体大小
plt.show() #显示图片
面向对象

fig, ax = plt.subplots(figsize=(15,3)) #创建坐标轴对象


ax.plot(x, y) #调用坐标轴的绘图方法
ax.set_xlim(0, 10) # 调用坐标轴的设置x轴上下限的方法
ax.set_ylim(-3, 8)
ax.set_xlabel('X axis') # 调用坐标轴的设置x轴标签的方法
ax.set_ylabel('Y axis',size = 20) # 调用坐标轴的设置y轴标签的方法
ax.set_title('Line Plot',size = 30) # 调用坐标轴的设置标题的方法
plt.show()
on

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

count 11.000000 11.000000


on

on
mean 9.000000 7.500909

std 3.316625 2.031568

min 4.000000 4.260000


th

th
25% 6.500000 6.315000

50% 9.000000 7.580000

75% 11.500000 8.570000


Py

max
Py
14.000000 10.840000

dataset_2.describe()


显示结果:

x y


count 11.000000 11.000000

mean 9.000000 7.500909

std 3.316625 2.031657


min 4.000000
程 3.100000

25% 6.500000 6.695000



50% 9.000000 8.140000

75% 11.500000 8.950000

max 14.000000 9.260000


dataset_3.describe()

显示结果:

x y

count 11.000000 11.000000

mean 9.000000 7.500000

std 3.316625 2.030424

min 4.000000 5.390000

25% 6.500000 6.250000

50% 9.000000 7.110000

75% 11.500000 7.980000

max 14.000000 12.740000

dataset_4.describe()
显示结果:

x y

count 11.000000 11.000000

mean 9.000000 7.500909

std 3.316625 2.030579

min 8.000000 5.250000

25% 8.000000 6.170000

50% 8.000000 7.040000

75% 8.000000 8.190000

max 19.000000 12.500000

从数据的统计量看,变量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())

显示结果:

total_bill tip sex smoker day time size


0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

单变量

在统计学属于中,‘单变量’(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

This tremendous 100% varietal wine hails Napa Cabernet


0 US Martha's Vineyard 96 235.0 California Napa Heitz
from ... Valley Sauvignon

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

Mac Watson honors the memory of a Knights Sauvignon


2 US Special Selected Late Harvest 96 90.0 California Sonoma Macauley
wine once ma... Valley Blanc

2.1 柱状图和分类数据
条形图可以说是最简单的数据可视化。 在我们的案例中,将所有的葡萄酒品牌按照产区分类,看看哪个产区的葡萄酒品种多:

# figsize 绘图区域大小, fontsize 字体大小 color 颜色


text_kwargs=dict(figsize = (16,8),fontsize=20,color =
['b','orange','g','r','purple','brown','pink','gray','cyan','y'])
reviews['province'].value_counts().head(10).plot.bar(**text_kwargs)

显示结果:
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[reviews['price'] < 200]['price'].plot.hist()


显示结果:

直方图看起来很像条形图。 直方图是一种特殊的条形图,它可以将数据分成均匀的间隔,并用条形图显示每个间隔中有多少行。 唯一
的分析差异是,它不是代表每个值的每个条形,而是代表值的范围。

直方图缺点。 将数据分成均匀的间隔区间,所以它们对歪斜的数据的处理不是很好:

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...

A big, powerful wine that sums up the


Vineyard
91 2013.0 California
Seco Coast
程 Chardonnay

Bordeaux-style Red
Blair

34920 France NaN 99 2300.0 Bordeaux Pauillac NaN Château Latour


richness... Blend

A massive wine for Margaux, packed with Bordeaux-style Red Château


34922 France NaN 98 1900.0 Bordeaux Margaux NaN
tannin... Blend Margaux


数据倾斜:

当数据在某个维度上分布不均匀,称为数据倾斜
一共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 散点图
最简单的两个变量可视化图形是散点图,散点图中的一个点,可以表示两个变量

reviews[reviews['price'] < 100].sample(100).plot.scatter(x='price', y='points')

显示结果:
调整图形大小,字体大小,由于pandas的绘图功能是对Matplotlib绘图功能的封装,所以很多参数pandas 和 matplotlib都一样

reviews[reviews['price'] < 100].sample(100).plot.scatter(x='price', y='points',figsize=(14,8),fontsize =


16)

修改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万条)都绘制到散点图上,会有很多点
重叠在一起,不方便观察

reviews[reviews['price'] < 100].plot.scatter(x='price', y='points',figsize=(12,8))

显示结果:
on

on
由于散点图的缺点,因此散点图最适合使用相对较小的数据集以及具有大量唯一值的变量。
有几种方法可以处理过度绘图。 一:对数据进行采样。 另一种内置于熊猫的有趣方法是使用我们的下一个绘图类型,即
hexplot。

3.2 hexplot
th

th
hexplot将数据点聚合为六边形,然后根据其内的值为这些六边形上色:

reviews[reviews['price'] < 100].plot.hexbin(x='price', y='points', gridsize=15,figsize=(14,8))


Py

显示结果:
Py





上图x轴坐标缺失,属于bug,可以通过调用matplotlib的api添加x坐标

fig, axes = plt.subplots(ncols=1, figsize = (12,8))


reviews[reviews['price'] < 100].plot.hexbin(x='price', y='points', gridsize=15,ax = axes)
axes.set_xticks([0,20,40,60,80,100])

显示结果:
on

on
该图中的数据可以和散点图中的数据进行比较,但是hexplot能展示的信息更多
从hexplot中,可以看到《葡萄酒杂志》(Wine Magazine)评论的葡萄酒瓶大多数是87.5分,价格20美元
Hexplot和散点图可以应用于区间变量和/或有序分类变量的组合。

3.3 堆叠图(Stacked plots)


th

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

从结果中看出,最受欢迎的葡萄酒是,Chardonnay(霞多丽白葡萄酒),Pinot Noir(黑皮诺),Cabernet Sauvignon(赤霞珠),


Red Blend(混酿红葡萄酒) ,Bordeaux-style Red Blend (波尔多风格混合红酒)
从数据中取出最常见的五种葡萄酒

top_5_wine = reviews[reviews.variety.isin(['Chardonnay','Pinot Noir','Cabernet Sauvignon','Red


Blend','Bordeaux-style Red Blend'])]

通过透视表找到每种葡萄酒中,不同评分的数量

# 透视表计数
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

80 5.0 89.0 70.0 36.0 75.0

81 23.0 160.0 154.0 83.0 108.0

82 83.0 436.0 523.0 296.0 233.0

83 122.0 571.0 686.0 350.0 366.0

84 334.0 925.0 1170.0 757.0 623.0

85 379.0 1058.0 1299.0 903.0 608.0

86 467.0 1205.0 1525.0 1260.0 919.0

87 679.0 1589.0 1887.0 1784.0 1375.0

88 741.0 1160.0 1513.0 1586.0 1366.0

89 724.0 920.0 1039.0 1223.0 1013.0

90 901.0 1341.0 1435.0 1646.0 1131.0

91 821.0 825.0 938.0 1124.0 881.0

92 733.0 1018.0 953.0 1173.0 620.0


on

on
93 556.0 653.0 671.0 992.0 395.0

94 338.0 440.0 325.0 621.0 190.0

95 220.0 233.0 188.0 269.0 88.0

96 124.0 102.0 73.0 105.0 30.0


th

th
97 67.0 56.0 23.0 56.0 27.0

98 22.0 12.0 7.0 20.0 6.0


Py

99 8.0
Py 4.0 NaN 2.0 5.0

100 NaN 3.0 3.0 2.0 2.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()

显示结果

Date ARG BRA ESP FRA GER ITA

1993-08-08 5.0 8.0 13.0 12.0 1.0 2.0

1993-09-23 12.0 1.0 14.0 7.0 5.0 2.0

1993-10-22 9.0 1.0 7.0 14.0 4.0 3.0

1993-11-19 9.0 4.0 7.0 15.0 3.0 1.0

1993-12-23 8.0 3.0 5.0 15.0 1.0 2.0

seaborn 绘图

import seaborn as sns


plt.figure(figsize=(16,6))
sns.lineplot(data=fifa_data)

显示结果
2 Seaborn绘制单变量图

2.1 直方图
使用sns.distplot创建直方图,如下所示:

import seaborn as sns


on

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在每个轴上创建包含单个变量的散点图。

joint = sns.jointplot(x='total_bill',y='tip',data = tips)


joint.set_axis_labels(xlabel = 'Total Bill',ylabel='Tip')


# 添加标题,设置字号
# 移动轴域上方的文字
joint.fig.suptitle('Joint Plot of Total Bill and Tip',fontsize = 10,y=1.03)

显示结果
3.2 蜂巢图
on

on
使用Seaborn的jointplot绘制蜂巢图,和使用matplotlib的hexbin函数进行绘制

joint = sns.jointplot(x='total_bill',y='tip',data = tips,kind='hex')


joint.set_axis_labels(xlabel = 'Total Bill',ylabel='Tip')
th

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数据

#col 用于指定分面变量 这里指定 'dataset' 列中,每个取值创建一张散点图


#col_wrap 用于指定绘制的图形有几列
anscombe_plot = sns.lmplot(x = 'x',y='y',data = anscombe,fit_reg = False,col = 'dataset',col_wrap = 2)

显示结果
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中的绘图方法

facet = sns.FacetGrid(tips,col = 'day',hue = 'sex',size=5)


facet.map(plt.scatter,'total_bill','tip')
facet.add_legend()

显示结果
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简介

ECharts 是一个使用 JavaScript 实现的开源可视化库,涵盖各行业图表,满足各种需求。


ECharts 遵循 Apache-2.0 开源协议,免费商用。
ECharts 兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等)及兼容多种设备,可随时随地任性展示。

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')

company_name:公司名字,url,工作的网址,job_name : 工作名字,city:工作所在城市,salary:工资 experience:经验,



company_area:公司所在行业,company_size:公司规模,description:工作描述

2.2 哪些城市在数据分析机会多


按城市分组

city_job_list = job.groupby('city')['url'].count().sort_values(ascending = False)


city_job_top20 = city_job_list.head(20)

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 绘制柱状图

from pyecharts import options as opts


from pyecharts.charts import Bar
c = (
Bar() #创建柱状图
.add_xaxis(city_job_top20.index.tolist()) #添加x轴数据
.add_yaxis('数据分析就业岗位数量', city_job_top20.values.tolist())#添加y轴数据
.set_global_opts( #设置全局参数
title_opts=opts.TitleOpts(title='一周内Python就业岗位数量'), #设置标题
datazoom_opts=opts.DataZoomOpts(),#添加缩放条
)
)
c.render_notebook() # 在juypter notebook中显示

显示结果:
on

on
th

th
Py

Py


从结果中可以看出,北京上海广州深圳等一线城市,对数据分析的需求最为旺盛


2.3 哪些公司在招聘Python程序员
根据公司名字对数据进行分组

company_list = job.groupby('company_name')['url'].count().sort_values(ascending = False)



company_list = company_list.head(100)
company_list


显示结果:

company_name
北京字节跳动科技有限公司 213
武汉益越商务信息咨询有限公司 85

北京融汇天诚投资管理有限公司 81
字节跳动 67
腾讯科技(深圳)有限公司 43
...
北京华融泰克科技有限公司 7
广发银行股份有限公司信用卡中心 7
软通动力信息技术(集团)有限公司 7
中国平安人寿保险股份有限公司 7
墨博(湖南)文化传媒有限公司 7
Name: url, Length: 100, dtype: int64

pyecharts绘制词云图

from pyecharts.charts import WordCloud


c = (
WordCloud()#创建词云图对象
.add(series_name='哪些公司在招聘数据分析程序员', #添加标题
data_pair=list(zip(company_list.index.tolist(),company_list.values.tolist())),
word_size_range=[6, 40])#指定文字大小,注意如果字体太大可能显示不全
.set_global_opts(#设置全局参数 标题,字号
title_opts=opts.TitleOpts(title='哪些公司在招聘数据分析程序员',
title_textstyle_opts=opts.TextStyleOpts(font_size=23))
)
)
c.render_notebook()

显示结果:

on

on
2.4 岗位数量与平均起薪分布
th

th
数据清洗,提取起薪

原始数据中 大多数薪资都以'6000-18000' 形式显示,需要对数据进行处理,提取出起薪,可以使用正则表达式,配合apply自定


义函数,将工作起薪提取出来
Py

# 数据清洗 提取起薪
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)

提取起薪数据之前,首先处理缺失值,将缺失数据去掉并提取起薪

job.dropna(subset = ['salary'],inplace = True)



job['salary_down'] = job.salary.apply(get_salary_down)
job['salary_down'].value_counts().sort_index()

显示结果:

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()

显示结果:

city salary_down job_count

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
圆环图展示结果

from pyecharts.charts import Pie


c = (
Pie()
.add(
series_name="经验要求",
data_pair=[list(z) for z in zip(
job['exp'].value_counts().index.tolist(),# 准备数据
job['exp'].value_counts().values.tolist()
)],
radius=["50%", "70%"],#圆环图,大环小环的半径大小
label_opts=opts.LabelOpts(is_show=False, position="center"),
)#设置图例位置
.set_global_opts(
title_opts=opts.TitleOpts(title="数据分析工作经验要求"),
legend_opts=opts.LegendOpts(pos_left="right", orient="vertical"))
.set_series_opts(
tooltip_opts=opts.TooltipOpts(#鼠标滑过之后弹出文字格式
trigger="item",
formatter="{a} <br/>{b}: {c} ({d}%)"
),
)
)
on

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的官方文档和案例

18_APP Store 数据分析案例


学习目标
掌握描述性数据分析流程
熟练使用pandas、seaborn进行数据分析和可视化

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

0 Unnamed: 0 7197 non-null int64



1 id 7197 non-null int64
2 track_name 7197 non-null object
3 size_bytes 7197 non-null int64
4 price 7197 non-null float64


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

9 ipadSc_urls 7197 non-null int64


10 lang 7197 non-null int64
dtypes: float64(2), int64(7), object(2)
memory usage: 618.6+ KB

app.head()

显示结果:

Unnamed: 0 id track_name size_bytes price rating_count_tot user_rating prime_genre sup_devices ipadSc_urls lang

0 0 281656475 PAC-MAN Premium 100788224 3.99 21292 4.0 Games 38 5 10

1 1 281796108 Evernote - stay organized 158578688 0.00 161065 4.0 Productivity 37 5 23

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

4 4 282935706 Bible 92774400 0.00 985920 4.5 Reference 37 5 45

发现了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

count 7.197000e+03 7.197000e+03 7197.000000 7.197000e+03 7197.000000 7197.000000 7197.000000 7197.000000

mean 8.631310e+08 1.991345e+08 1.726218 1.289291e+04 3.526956 37.361817 3.707100 5.434903

std 2.712368e+08 3.592069e+08 5.833006 7.573941e+04 1.517948 3.737715 1.986005 7.919593

min 2.816565e+08 5.898240e+05 0.000000 0.000000e+00 0.000000 9.000000 0.000000 0.000000

25% 6.000937e+08 4.692275e+07 0.000000 2.800000e+01 3.500000 37.000000 3.000000 1.000000

50% 9.781482e+08 9.715302e+07 0.000000 3.000000e+02 4.000000 37.000000 5.000000 1.000000

75% 1.082310e+09 1.819249e+08 1.990000 2.793000e+03 4.500000 38.000000 5.000000 8.000000

max 1.188376e+09 4.025970e+09 299.990000 2.974676e+06 5.000000 47.000000 5.000000 75.000000

考虑将sizebytes变成mb,新增数据

app['size_mb'] = app['size_bytes'] / (1024 * 1024.0)


app.size_mb.describe()

显示结果:

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

根据价格新增标签

app['paid'] = app['price'].apply(lambda x: 1 if x > 0 else 0)


#lambda阐述规则,X为price,为paid赋值,即当price>0,paid为1,其他情况下,paid为0
th

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()

显示结果:

count mean std min 25% 50% 75% max


on

on
price_new

<2 5405.0 0.361981 0.675318 0.00 0.00 0.00 0.00 1.99

<10 1695.0 4.565811 1.864034 2.99 2.99 3.99 4.99 9.99

<300 97.0 28.124021 38.886220 11.99 14.99 19.99 24.99 299.99


th

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

Business 57.0 5.116316 10.247031 0.0 0.0 2.99 4.99 59.99

Catalogs 10.0 0.799000 2.526660 0.0 0.0 0.00 0.00 7.99



Education 453.0 4.028234 18.725946 0.0 0.0 2.99 2.99 299.99

Entertainment 535.0 0.889701 1.454022 0.0 0.0 0.00 1.99 9.99

Finance 104.0 0.421154 1.108990 0.0 0.0 0.00 0.00 5.99



Food & Drink 63.0 1.552381 3.972119 0.0 0.0 0.00 1.49 27.99

Games 3862.0 1.432923 2.486609 0.0 0.0 0.00 1.99 29.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

Medical 23.0 8.776087 10.788269 0.0 0.0 3.99 16.99 34.99

Music 138.0 4.835435 8.915667 0.0 0.0 0.99 4.99 49.99


Navigation 46.0 4.124783 11.565818 0.0 0.0 0.99 3.74 74.99

News 75.0 0.517733 1.127771 0.0 0.0 0.00 0.00 3.99

Photo & Video 349.0 1.473295 2.280703 0.0 0.0 0.99 1.99 22.99

Productivity 178.0 4.330562 8.747042 0.0 0.0 1.99 4.99 99.99

Reference 64.0 4.836875 8.285100 0.0 0.0 1.99 4.99 47.99

Shopping 122.0 0.016311 0.180166 0.0 0.0 0.00 0.00 1.99

Social Networking 167.0 0.339880 1.142210 0.0 0.0 0.00 0.00 9.99

Sports 114.0 0.953070 2.419084 0.0 0.0 0.00 0.99 19.99

Travel 81.0 1.120370 2.183772 0.0 0.0 0.00 0.99 9.99

Utilities 248.0 1.647621 2.628541 0.0 0.0 0.99 1.99 24.99

Weather 72.0 1.605417 1.831316 0.0 0.0 0.99 2.99 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()

显示结果:

count mean std min 25% 50% 75% max

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)) 并没有起作用

可以通过height 和 aspect 两个参数调整图片大小

使用 catplot() 、 relplot() (以及 pairplot() , lmplot() 和 jointplot() ), 使用 height 关键字来控制图片高度


aspect 控制宽高比例

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()

显示结果:

count mean std min 25% 50% 75% max

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 price size_mb


Py

user_rating 1.000000
Py
0.073237 0.066160

price 0.073237 1.000000 0.314386



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

2 70 深圳 线下 Male >=60 Weekday T恤 2 178.0 2 2 49

3 658 深圳 线下 Female 25-29 Weekday T恤 1 59.0 1 1 49

4 229 深圳 线下 Male 20-24 Weekend 袜子 2 65.0 2 3 9



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

quant 22293 non-null int64


unit_cost 22293 non-null int64
dtypes: float64(1), int64(5), object(6)
memory usage: 2.0+ MB

从上面结果中看出,数据没有缺失

uniqlo.describe()

显示结果:

store_id customer revenue order quant unit_cost

count 22293.000000 22293.000000 22293.000000 22293.000000 22293.000000 22293.000000

mean 335.391558 1.629480 159.531371 1.651998 1.858072 46.124658

std 230.236167 1.785605 276.254066 1.861480 2.347301 19.124347

min 19.000000 1.000000 -0.660000 1.000000 1.000000 9.000000

25% 142.000000 1.000000 64.000000 1.000000 1.000000 49.000000

50% 315.000000 1.000000 99.000000 1.000000 1.000000 49.000000

75% 480.000000 2.000000 175.000000 2.000000 2.000000 49.000000

max 831.000000 58.000000 12538.000000 65.000000 84.000000 99.000000

从结果中看出,revenue 销售金额,有异常值,-0.66 / 12538,可以进一步查看


uniqlo[uniqlo.revenue<1]

显示结果:

store_id city channel gender_group age_group wkd_ind product customer revenue order quant unit_cost

77 231 广州 线下 Female >=60 Weekend 毛衣 1 0.00 1 1 99

837 231 广州 线下 Female 40-44 Weekend 袜子 1 0.00 1 1 9

1137 336 西安 线下 Female 45-49 Weekend 当季新品 1 0.00 1 1 59

1405 231 广州 线下 Female 35-39 Weekday 当季新品 1 0.00 1 1 59

1411 231 广州 线下 Female 30-34 Weekend 当季新品 1 0.00 1 1 59

2475 336 西安 线下 Female 45-49 Weekend T恤 1 0.00 1 1 49

2606 231 广州 线下 Female 20-24 Weekday T恤 1 0.00 1 1 49

2863 231 广州 线下 Male 40-44 Weekend 当季新品 1 0.00 1 1 59

3479 231 广州 线下 Male >=60 Weekend 牛仔裤 1 0.00 1 1 69

4168 231 广州 线下 Female 25-29 Weekend 袜子 1 0.00 1 1 9

4390 231 广州 线下 Female 25-29 Weekend 袜子 1 0.00 1 1 9

4858 231 广州 线下 Female 20-24 Weekend T恤 1 0.00 1 1 49

5048 231 广州 线下 Male 30-34 Weekday 当季新品 1 0.00 1 1 59

7656 231 广州 线下 Female 50-54 Weekday T恤 1 0.00 1 1 49

9278 336 西安 线下 Female <20 Weekend T恤 1 0.00 1 1 49

10558 336 西安 线下 Female 30-34 Weekday T恤 1 0.00 1 1 49

11120 336 西安 线下 Female 25-29 Weekend 袜子 1 0.00 1 1 9

12703 231 广州 线下 Female 40-44 Weekday T恤 1 0.00 1 1 49

12764 231 广州 线下 Male 50-54 Weekend T恤 1 0.00 1 1 49

12778 336 西安 线下 Female 20-24 Weekend T恤 1 0.00 1 1 49

13323 336 西安 线下 Female 40-44 Weekday 牛仔裤 1 0.00 1 1 69

16261 231 广州 线下 Female 35-39 Weekday T恤 1 0.00 1 1 49


on

on
17259 336 西安 线下 Female 20-24 Weekday 配件 1 0.00 1 1 29

17606 231 广州 线下 Female 20-24 Weekend 当季新品 1 0.00 1 1 59

17967 336 西安 线下 Male 50-54 Weekday 短裤 1 0.00 1 1 19

19032 231 广州 线下 Female 25-29 Weekday 短裤 1 0.00 1 1 19

19486 336 西安 线下 Male >=60 Weekday T恤 1 0.00 1 1 49

20049 91 武汉 线上 Female 55-59 Weekday 运动 1 -0.66 1 2 49


th

th
20180 336 西安 线下 Male 20-24 Weekday T恤 1 0.00 1 1 49

21404 336 西安 线下 Male >=60 Weekday 短裤 1 0.00 1 1 19

21618 231 广州 线下 Male 40-44 Weekend 袜子 1 0.00 1 1 9

进一步查看之后发现,一部分订单收入为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

3008 46 武汉 线下 Female 25-29 Weekend 当季新品 44 7919.00 48 48 59

4230 796 深圳 线下 Female 35-39 Weekday 当季新品 29 5122.00 30 31 59

6595 20 深圳 线下 Female 25-29 Weekday 当季新品 5 5947.00 5 30 59



9628 19 南京 线下 Female 45-49 Weekday 当季新品 42 12538.00 45 70 59

9699 437 重庆 线下 Female 35-39 Weekday 当季新品 58 10498.28 65 65 59

11769 19 南京 线下 Female 30-34 Weekday 当季新品 22 5771.00 22 29 59

12729 335 上海 线下 Female 50-54 Weekday T恤 1 6636.00 4 84 49

15136 360 Female 30-34 Weekday 38 6150.00 39 39 59


成都 线下 当季新品

16309 146 杭州 线下 Female 25-29 Weekday 当季新品 45 8836.00


程 54 55 59

16331 50 武汉 线上 Female 20-24 Weekday 当季新品 32 5039.59 34 34 59

21221 245 杭州 线下 Female 35-39 Weekday 当季新品 32 5241.43 32 33 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恤销售记录条数最多,其次是当季新品,袜子...

uniqlo.pivot_table(index = 'product',values = 'quant',aggfunc = 'sum').sort_values(by=['quant'],ascending =


False)

显示结果:
quant

product

T恤 18425

当季新品 5338

配件 4622

袜子 3639

短裤 2821

牛仔裤 2432

运动 1794

毛衣 1356

裙子 995

从上面结果中看出销售件数的排行
进一步拆解,按城市拆解销量

uniqlo.pivot_table(index = 'product',columns='city',values = 'quant',aggfunc='sum').sort_values(by=['上


海'],ascending = False)
on

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

牛仔裤 307 46 52 194 178 388 415 477 152 223

短裤 306 87 49 266 179 456 490 608 174 206



运动 171 31 17 174 125 351 282 364 118 161

毛衣 131 37 44 149 92 238 202 248 105 110

裙子 102 27 26 107 77 163 165 201 71 56



对城市拆解后,再进一步按线上线下拆解

uniqlo.pivot_table(index = 'product',columns=['city','channel'],values = 'quant',aggfunc='sum')



显示结果:

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

毛衣 27 104 37 44 85 64 92 238 115 87 248 5 100 16 94


牛仔裤 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

裙子 34 68 27 26 44 63 77 163 62 103 201 17 54 1 55

运动 42 129 31 17 107 67 125 351 103 179 364 25 93 18 143

配件 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天,所以整体看
通过数据透视表,查看不同城市,周间周末销量情况

wkd_sales = uniqlo.pivot_table(index = 'wkd_ind',columns=['city'],values = 'quant',aggfunc='sum')


wkd_sales

显示结果:

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

revenue 1.00000 0.14844


th

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

0 658 深圳 线下 Female 25-29 Weekday 当季新品 4 796.0 4 4 59 199.000000

1 146 杭州 线下 Female 25-29 Weekday 运动 1 149.0 1 1 49 149.000000

2 70 深圳 线下 Male >=60 Weekday T恤 2 178.0 2 2 49 89.000000


3 658 深圳 线下 Female 25-29 Weekday T恤 1 59.0 1 1 49 59.000000

4 229 深圳 线下 Male 20-24 Weekend 袜子 2 65.0 2 3 9 21.666667

#添加单件收入列,使用单件收入和单位成本计算相似度
uniqlo2.loc[:,'rev_per_goods'] = uniqlo2['revenue']/uniqlo2['quant']
uniqlo2[['rev_per_goods','unit_cost']].corr()

显示结果:

rev_per_goods unit_cost

rev_per_goods 1.000000 0.503499

unit_cost 0.503499 1.000000

从结果中看出,单件收入和单位成本之间相关性还是比较强的,绘制热力图

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 # 时间库

import numpy as np # numpy库


import pandas as pd # pandas库

import pymysql # mysql连接库

from pyecharts import Bar3D # 3D柱形图

用到了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) # 数据类型

输出结果:

[data summary for =============2015===============]


Overview:
会员ID 订单号 提交日期 订单金额
0 15278002468 3000304681 2015-01-01 499.0
1 39236378972 3000305791 2015-01-01 2588.0
2 38722039578 3000641787 2015-01-01 498.0
3 11049640063 3000798913 2015-01-01 1572.0
DESC:
会员ID 订单号 订单金额
count 3.077400e+04 3.077400e+04 30774.000000
mean 2.918779e+10 4.020414e+09 960.991161
std 1.385333e+10 2.630510e+08 2068.107231
min 2.670000e+02 3.000305e+09 0.500000
25% 1.944122e+10 3.885510e+09 59.000000
50% 3.746545e+10 4.117491e+09 139.000000
75% 3.923593e+10 4.234882e+09 899.000000
on

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

2 34190994969 4324911251 2017-01-01 189.0


3 38986333210 4324911283 2017-01-01 169.0
DESC:
会员ID 订单号 订单金额
count 5.083900e+04 5.083900e+04 50839.000000
mean 2.882368e+10 4.332466e+09 963.587872
std 1.409416e+10 4.404350e+06 2178.727261
min 2.780000e+02 4.324911e+09 0.300000
25% 1.869274e+10 4.328415e+09 59.000000
50% 3.688044e+10 4.331989e+09 149.000000
75% 3.923020e+10 4.337515e+09 898.000000
max 3.954554e+10 4.338764e+09 123609.000000
NA records 0
Dtypes 会员ID int64
订单号 int64
提交日期 datetime64[ns]
订单金额 float64
dtype: object
[data summary for =============2018===============]
Overview:
会员ID 订单号 提交日期 订单金额
0 39229691808 4338764262 2018-01-01 3646.0
1 39293668916 4338764363 2018-01-01 3999.0
2 35059646224 4338764376 2018-01-01 10.1
3 1084397 4338770013 2018-01-01 828.0
DESC:
会员ID 订单号 订单金额
count 8.134900e+04 8.134900e+04 81348.000000
mean 2.902317e+10 4.348372e+09 966.582792
std 1.404116e+10 4.183774e+06 2204.969534
min 2.780000e+02 4.338764e+09 0.000000
25% 1.902755e+10 4.345654e+09 60.000000
50% 3.740121e+10 4.349448e+09 149.000000
75% 3.923380e+10 4.351639e+09 899.000000
max 3.954614e+10 4.354235e+09 174900.000000
NA records 1
Dtypes 会员ID int64
订单号 int64
提交日期 datetime64[ns]
订单金额 float64
dtype: object
[data summary for =============会员等级===============]
Overview:
会员ID 会员等级
0 100090 3
1 10012905801 1
2 10012935109 1
3 10013498043 1
DESC:
会员ID 会员等级
count 1.543850e+05 154385.000000
on

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

0 15278002468 3000304681 2015-01-01 499.0 2015-12-31 364 2015

1 39236378972 3000305791 2015-01-01 2588.0 2015-12-31 364 2015

2 38722039578 3000641787 2015-01-01 498.0 2015-12-31 364 2015

3 11049640063 3000798913 2015-01-01 1572.0 2015-12-31 364 2015

4 35038752292 3000821546 2015-01-01 10.1 2015-12-31 364 2015

汇总所有数据: 将4年的数据使用pd.concat方法合并为一个完整的dataframe data_merge,后续的所有计算都能基于同一个


dataframe进行,而不用写循环代码段对每个年份的数据单独计算

获取各自年份数据:
先计算各自年份的最大日期与每个行的日期的差,得到日期间隔
再增加一列新的字段,为每个记录行发生的年份,使用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

1 2015 282 251 1 29.7

2 2015 283 340 1 5398.0



3 2015 343 300 1 118.0

4 2015 525 37 3 213.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

输出结果:

count mean std min 25% 50% 75% max

r 148591.0 165.524043 101.988472 0.0 79.0 156.0 255.0 365.0

f 148591.0 1.365002 2.626953 1.0 1.0 1.0 1.0 130.0

m 148591.0 1323.741329 3753.906883 1.5 69.0 189.0 1199.0 206251.8

# 定义区间边界
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

0 111 2015 2180


1 111 2016
程 1498

2 111 2017 3169

3 111 2018 2271



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图,在修改弹出提示信息内容时,需要注意字符串拼接的格式

You might also like