数据管理与数据清洗

  • 企业数据管理的内容和范畴通常包含交易数据主数据以及元数据
  • 交易数据:用于纪录业务事件,如客户订单,投诉记录,客服申请等,以描述在某一个时间点上业务发生的行为(事件)

数据管理概述

主数据管理 Master Data

  • 主数据是相对于交易数据而言的一种属性数据。企业主数据指企业核心业务对象以及对象之间关系,包括但不限于:客户、门店、渠道、部门、员工、供应商、产品、地址、合同、BOM等等。如:
    • 供应商:组织机构代码、供应商名称、注册资金、开户行、账号
    • 采购合同:合同名称、编号、律审编号(外键,同时是法律系统表的主键)、合同金额、供应商、采购标的
  • 主数据管理重要性
    • 消除数据冗余
    • 提升数据处理效率
  • 主数据管理的核心
    • 从多个业务系统中提取、整合最核心的、最需要共享的主数据,集中进行清洗和丰富
    • 以服务的方式将整合后的统一、完整和一致的主数据分发给各业务系统使用
    • 主数据可以跨业务、跨组织、跨系统被重复利用
    • 主数据变动由业务系统触发
  • 主数据通常在数据仓库建模中被定义为维度数据

元数据管理 Meta Data

  • 元数据是对于数据的描述,存储关于数据的数据信息
  • 元数据管理的范围将涵括数据产生、数据存储、数据加工和展现等各个环节的数据描述信息,帮助用户理解数据来龙去脉、关系及相关属性。
  • 技术元数据描述了数据的属性(名称、大小、数据类型等),或结构(长度、字段、数据列)等信息。
    • 数据结构:数据模式、表、视图、触发器、维度、层次结构、数据存储位置
    • 数据算法:度量和维的定义算法,数据粒度、主题领域、聚集、汇总、预定义的查询与报告
  • 业务元数据描述了数据系统中业务领域相关概念、关系和规则的数据,包括业务术语、信息分类、指标定义和业务规则等信息。
    • 数据映射:源数据和它们的内容、数据分割、数据提取、清理、转换等规则和数据刷新规则
  • 管理元数据描述了数据系统中管理领域相关概念、关系和规则的数据,主要包括人员角色、岗位职责和管理流程,如:相关数据(位于何处、关联关系、拥有者、权限配置)等信息。
    • 数据安全:用户授权和存取控制

数据流程

数据源

  • ERP类应用软件的底层关系型数据库
  • APP类程序的非关系型数据库
  • 文本文件
    • txt文件
    • excel文件
    • csv文件
  • web网页
  • 日志文件
  • 应用程序的API
  • 其他

数据目的地

  • 数据仓库
    • 事实表
    • 维度表
    • 模型:星型模型、雪花模型
  • 数据湖
    • Data lake这个术语由Pentaho公司的创始人兼首席技术官詹姆斯·狄克逊(James Dixon)提出,他对数据湖的解释是: 把你以前在磁带上拥有的东西倒入到数据湖,然后开始探索该数据。重要的只把需要的数据倒入到Hadoop;如果你想结合来自数据湖的信息和客户关系管理系统(CRM)里面的信息,我们就进行连接,只有需要时才执行这番数据结合。

    • 数据湖是一种在系统或存储库中以自然格式存储数据的方法,它有助于以各种模式和结构形式配置数据,通常是对象块或文件。数据湖的主要思想是对企业中的所有数据进行统一存储,从原始数据(源系统数据的精确副本)转换为用于报告、可视化、分析和机器学习等各种任务的目标数据。数据湖中的数据包括

      • 结构化数据(关系数据库数据)
      • 半结构化数据(CSV、XML、JSON等)
      • 非结构化数据(电子邮件,文档,PDF)
      • 二进制数据(图像、音频、视频)
    • 从而形成一个容纳所有形式数据的集中式数据存储。
  • 数据集市
    • 数据集市 Data Mart
    • 是满足特定的部门或者用户的需求,按照多维的方式进行存储,包括定义维度、需要计算的指标、维度的层次等,生成面向决策分析需求的数据立方体。
  • 数据中台
    • 阿里提出“大中台,小前台”,中台事业部包括:
      • 搜索事业部
      • 共享业务平台
      • 数据技术及产品部
    • 数据技术及产品部应是数据中台建设的核心部门。

    • 数据中台,实现数据的分层与水平解耦,沉淀公共的数据能力

    • 可分为三层,数据模型、数据服务与数据开发
      • 通过数据建模实现跨域数据整合和知识沉淀
      • 通过数据服务实现对于数据的封装和开放,快速、灵活满足上层应用的要求
      • 通过数据开发工具满足个性化数据和应用的需要
    • (中国移动浙江公司傅一平)
  • 其他:数据库分布式架构、大数据平台Hadoop

数据使用

  • 数据预测
  • 数据表格
  • 可视化图表

数据清洗分类

基于数据缺失、数据待规范的数据清洗,可以被定义为侠义数据清洗

基于数据结构设计、关联关系设计、数据粒度设计的操作,这类数据清洗工作可以被定义为广义数据清洗

侠义数据清洗

数据去重

  • 交易记录中如果存在重复记录,会导致汇总金额重复计算,导致错误
  • 属性记录中如果存在重复记录,会导致交易记录的重复关联,最终也会导致数据汇总错误
  • 属性记录中如果存在重复记录,还有可能导致关联失败
  • 通常避免重复记录的方法是在数据库表结构中设计关键字字段,关键字字段不可重复
  • 交易记录中通过会有时间戳字段,这种情况下也可以避免重复
  • 但是在进行数据ETL过程中,可能发生重复抽取,或没有选择关键字或时间戳字段而导致重复
  • 关系型数据库中避免重复的方法:
    • select distinct fields from tablename;
  • pandas DataFrame中避免重复的方法:
    • df.drop_duplicates(subset=None, keep='first', inplace=False)
      • 示例:df.drop_duplicates(["Seqno"],keep="last").head()
      • subset 用来指定特定的列,默认为所有列
      • keep='last' 保留最后一个
      • inplace=False 不改变原来的DataFrame(True时改变)
      • data.drop_duplicates() # data中一行元素全部相同时才去除
      • data.drop_duplicates(['a','b']) # data根据’a','b'组合列删除重复项,默认保留第一个出现的值组合。

清除空格

  • 有时数据库表的字段是char类型,char是固定长度,不足用空格填充,于是产生了空格
  • 有时从某处(譬如网络)抓取数据后直接插入数据库中,抓取的时候含有空格,导致数据库中数据含有空格
  • 尽管外表看来都是相同的内容,但是实际因为含有或不含有空格的原因导致无法匹配
  • 空格可能在内容的前端、后端,或者中部
  • 关系型数据库 postgresql 去除空格的方法:
    • 去除首尾空格 select * from trim(' 空格 与 空格 ');
    • 去除所有空格 select * from replace(' 空格 与 空格 ',' ','');
  • Python中去除空格的方法:
    • str.strip():去除左右两边空格
    • lstrip():去除左边空格
    • rstrip():去除右边空格
    • df['xxx'].str.strip( )
    • df['xxx'].str.replace(' ',''):替换所有的空格
    • series.str.replace() & df.replace()的区别:
      • s.str.replace 可以替换含有空格的单元内容
      • 而 df.replace()不能

维度信息唯一性

  • 数据出现重复,可能是交易数据重复,也可能是维度数据重复
  • 维度数据可能真实存在重复,譬如对于员工维度,可能存在同名的员工
  • 方法是使用“工号”作为员工唯一的标识字段

主键和外键约束

  • 主键和外键是两种类型的约束,可用于强制关系型数据库中的数据完整性
  • 主键约束
    • 表通常具有包含唯一标识表中每一行的值的一列或一组列
    • 这样的一列或多列称为表的主键 (PK),用于强制表的实体完整性
    • 主键约束针对多列时,也就是组合主键约束
  • 外键约束
    • 外键 (FK) 是用于在两个表中的数据之间建立和加强链接的一列或多列的组合,可控制可在外键表中存储的数据
    • 一个表的主键值的一个或多个列被另一个表中的一个或多个列引用时,就在这两个表之间创建了链接
    • 这个列就成为第二个表的外键

缺失值

缺失值的处理方法

  • 人工补全:适合数据量少的情况
  • 删除:适用于样本较大的情况,样本较小时,可能会影响最终的分析结果
  • 根据数据的分布情况,可以采用均值中位数、或者众数进行数据填充
    • 数据均匀:均值法填充
    • 数据分布倾斜:中位数填充
  • 用模型计算值来代替缺失值。
    • 回归:基于完整的数据集建立回归方程。将已知属性值代入方程来估计未知属性值,以估计值来进行空值得填充。
    • 极大似然估计:缺失类型为随机缺失,假设模型对于完整的样本是正确的,通过观测数据的边际分布可以对缺失数据进行极大似然估计
  • 随机插补法:从总体中随机抽取某几个样本代替缺失样本。
  • 多重填补法:多重填补是针对单一填补而言,所谓单一填补,就是对每一缺失值填一个值,这类方法不能反映缺失数据的不确定性,因而容易导致标准误低估。比如回归方法填补,填补后,由于填补值是根据直线回归估计出来的,他们都在一条直线上,没有误差,标准误肯定低估了,因为正常情况下,所有的数据距离回归线总是有点误差的。。
  • 使用身份证件号码推算性别、籍贯、出生日期、年龄

Pandas缺失值处理

  • df.dropna()
  • df.fillna()
  • df.isnull()
  • df.isna()
准备数据
In [15]:
import numpy as np
import pandas as pd
# 函数eye()的作用是返回一个对角线diagonal上全是1,而其他位置全为0的一个二维数组
data = np.eye(6)
data
Out[15]:
array([[1., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 1.]])
In [16]:
# np.where(condition, x, y):满足条件(condition),输出x,不满足输出y。
# np.where(condition):只有条件 (condition),没有x和y,则输出满足条件 (即非0) 元素的坐标 (等价于numpy.nonzero)。
# 这里的坐标以tuple的形式给出,通常原数组有多少维,输出的tuple中就包含几个数组,分别对应符合条件元素的各维坐标。
datanan = np.where(data,data,np.nan)
datanan
Out[16]:
array([[ 1., nan, nan, nan, nan, nan],
       [nan,  1., nan, nan, nan, nan],
       [nan, nan,  1., nan, nan, nan],
       [nan, nan, nan,  1., nan, nan],
       [nan, nan, nan, nan,  1., nan],
       [nan, nan, nan, nan, nan,  1.]])
In [17]:
datapdnan = pd.DataFrame(datanan)
datapdnan
Out[17]:
0 1 2 3 4 5
0 1.0 NaN NaN NaN NaN NaN
1 NaN 1.0 NaN NaN NaN NaN
2 NaN NaN 1.0 NaN NaN NaN
3 NaN NaN NaN 1.0 NaN NaN
4 NaN NaN NaN NaN 1.0 NaN
5 NaN NaN NaN NaN NaN 1.0
isnull() & isna() 检测
In [18]:
datapdnan.isnull()
Out[18]:
0 1 2 3 4 5
0 False True True True True True
1 True False True True True True
2 True True False True True True
3 True True True False True True
4 True True True True False True
5 True True True True True False
In [19]:
datapdnan.isna()
Out[19]:
0 1 2 3 4 5
0 False True True True True True
1 True False True True True True
2 True True False True True True
3 True True True False True True
4 True True True True False True
5 True True True True True False
fillna()
In [20]:
datapd = datapdnan.fillna(method='ffill')
datapd
Out[20]:
0 1 2 3 4 5
0 1.0 NaN NaN NaN NaN NaN
1 1.0 1.0 NaN NaN NaN NaN
2 1.0 1.0 1.0 NaN NaN NaN
3 1.0 1.0 1.0 1.0 NaN NaN
4 1.0 1.0 1.0 1.0 1.0 NaN
5 1.0 1.0 1.0 1.0 1.0 1.0
In [21]:
datapdnan.fillna(method='bfill')
Out[21]:
0 1 2 3 4 5
0 1.0 1.0 1.0 1.0 1.0 1.0
1 NaN 1.0 1.0 1.0 1.0 1.0
2 NaN NaN 1.0 1.0 1.0 1.0
3 NaN NaN NaN 1.0 1.0 1.0
4 NaN NaN NaN NaN 1.0 1.0
5 NaN NaN NaN NaN NaN 1.0
dropna()
In [22]:
datapd
Out[22]:
0 1 2 3 4 5
0 1.0 NaN NaN NaN NaN NaN
1 1.0 1.0 NaN NaN NaN NaN
2 1.0 1.0 1.0 NaN NaN NaN
3 1.0 1.0 1.0 1.0 NaN NaN
4 1.0 1.0 1.0 1.0 1.0 NaN
5 1.0 1.0 1.0 1.0 1.0 1.0
In [23]:
#按行删除:存在空值,即删除该行
datapd.dropna()
Out[23]:
0 1 2 3 4 5
5 1.0 1.0 1.0 1.0 1.0 1.0
In [24]:
#按行删除:所有数据都为空值时,即删除该行
datapd.dropna(how='all')
Out[24]:
0 1 2 3 4 5
0 1.0 NaN NaN NaN NaN NaN
1 1.0 1.0 NaN NaN NaN NaN
2 1.0 1.0 1.0 NaN NaN NaN
3 1.0 1.0 1.0 1.0 NaN NaN
4 1.0 1.0 1.0 1.0 1.0 NaN
5 1.0 1.0 1.0 1.0 1.0 1.0
In [25]:
#按列删除:保留至少有3个非NaN值的列
datapd.dropna(axis='columns', thresh=3)
Out[25]:
0 1 2 3
0 1.0 NaN NaN NaN
1 1.0 1.0 NaN NaN
2 1.0 1.0 1.0 NaN
3 1.0 1.0 1.0 1.0
4 1.0 1.0 1.0 1.0
5 1.0 1.0 1.0 1.0
In [26]:
datapd
Out[26]:
0 1 2 3 4 5
0 1.0 NaN NaN NaN NaN NaN
1 1.0 1.0 NaN NaN NaN NaN
2 1.0 1.0 1.0 NaN NaN NaN
3 1.0 1.0 1.0 1.0 NaN NaN
4 1.0 1.0 1.0 1.0 1.0 NaN
5 1.0 1.0 1.0 1.0 1.0 1.0
In [27]:
#设置子集:删除第1、2列有空值的行
datapd.dropna(axis='index', subset=[0,1,2])
Out[27]:
0 1 2 3 4 5
2 1.0 1.0 1.0 NaN NaN NaN
3 1.0 1.0 1.0 1.0 NaN NaN
4 1.0 1.0 1.0 1.0 1.0 NaN
5 1.0 1.0 1.0 1.0 1.0 1.0
In [28]:
#设置子集:删除第1、2、3行有空值的列
datapd.dropna(axis=1, how='any', subset=[2,3])
Out[28]:
0 1 2
0 1.0 NaN NaN
1 1.0 1.0 NaN
2 1.0 1.0 1.0
3 1.0 1.0 1.0
4 1.0 1.0 1.0
5 1.0 1.0 1.0
In [29]:
#原地修改原dataframe,返回值为None.
datapd.dropna(inplace=True)
datapd
Out[29]:
0 1 2 3 4 5
5 1.0 1.0 1.0 1.0 1.0 1.0
再来一例
In [30]:
df = pd.DataFrame({'name':list('ABCDA'),'house':[1,1,2,3,3],'date':['2010-01-01','2010-06-09','2011-12-03','2011-04-05','2012-03-23']})
df
Out[30]:
name house date
0 A 1 2010-01-01
1 B 1 2010-06-09
2 C 2 2011-12-03
3 D 3 2011-04-05
4 A 3 2012-03-23
In [31]:
# 将date列改为时间类型:
df.date = pd.to_datetime(df.date)
df
Out[31]:
name house date
0 A 1 2010-01-01
1 B 1 2010-06-09
2 C 2 2011-12-03
3 D 3 2011-04-05
4 A 3 2012-03-23
In [32]:
df = pd.pivot_table(df, columns='name', index='date')
df
Out[32]:
house
name A B C D
date
2010-01-01 1.0 NaN NaN NaN
2010-06-09 NaN 1.0 NaN NaN
2011-04-05 NaN NaN NaN 3.0
2011-12-03 NaN NaN 2.0 NaN
2012-03-23 3.0 NaN NaN NaN
In [33]:
# 用常量来替换缺失值
df.replace(np.nan,10000)
Out[33]:
house
name A B C D
date
2010-01-01 1.0 10000.0 10000.0 10000.0
2010-06-09 10000.0 1.0 10000.0 10000.0
2011-04-05 10000.0 10000.0 10000.0 3.0
2011-12-03 10000.0 10000.0 2.0 10000.0
2012-03-23 3.0 10000.0 10000.0 10000.0
In [34]:
# 向下填充
df.fillna(method='ffill')
Out[34]:
house
name A B C D
date
2010-01-01 1.0 NaN NaN NaN
2010-06-09 1.0 1.0 NaN NaN
2011-04-05 1.0 1.0 NaN 3.0
2011-12-03 1.0 1.0 2.0 3.0
2012-03-23 3.0 1.0 2.0 3.0
In [35]:
# 向上填充
df = df.fillna(method='bfill')
df
Out[35]:
house
name A B C D
date
2010-01-01 1.0 1.0 2.0 3.0
2010-06-09 3.0 1.0 2.0 3.0
2011-04-05 3.0 NaN 2.0 3.0
2011-12-03 3.0 NaN 2.0 NaN
2012-03-23 3.0 NaN NaN NaN
In [36]:
# 压缩回原来的数据结构
df.stack().reset_index()
Out[36]:
date name house
0 2010-01-01 A 1.0
1 2010-01-01 B 1.0
2 2010-01-01 C 2.0
3 2010-01-01 D 3.0
4 2010-06-09 A 3.0
5 2010-06-09 B 1.0
6 2010-06-09 C 2.0
7 2010-06-09 D 3.0
8 2011-04-05 A 3.0
9 2011-04-05 C 2.0
10 2011-04-05 D 3.0
11 2011-12-03 A 3.0
12 2011-12-03 C 2.0
13 2012-03-23 A 3.0

异常值

  • 异常值,即在数据集中存在不合理的值,又称离群点

异常值识别

  • 属性值描述性统计
    • 年龄的区间在[0:200],如果样本中的年龄值不在该区间范围内,则表示该样本的年龄属性属于异常值。
  • 服从正态分布
    • 根据正态分布的定义可知,距离平均值3δ之外的概率为 $P(|x-μ|>3δ) <= 0.003$ ,这属于极小概率事件,在默认情况下我们可以认定,距离超过平均值3δ的样本是不存在的。 因此,当样本距离平均值大于3δ,则认定该样本为异常值。
  • 不服从正态分布
    • 当数据不服从正态分布时,可以通过远离平均距离多少倍标准差来判定,多少倍的取值需要根据经验和实际情况来决定。

异常值处理

  • 检测到了异常值,我们需要对其进行一定的处理。而一般异常值的处理方法可大致分为以下几种:
    • 删除含有异常值的记录:直接将含有异常值的记录删除;
    • 视为缺失值:将异常值视为缺失值,利用缺失值处理的方法进行处理;
    • 平均值修正:可用前后两个观测值的平均值修正该异常值;
    • 不处理:直接在具有异常值的数据集上进行数据挖掘;
  • 是否要删除异常值可根据实际情况考虑
    • 一些模型对异常值不很敏感,即使有异常值也不影响模型效果
    • 一些模型比如逻辑回归LR对异常值很敏感,如果不进行处理,可能会出现过拟合等非常差的效果
In [37]:
df=pd.DataFrame(np.random.random([20,7]))
df.describe(percentiles=[.2,.3,.7,.8,.9])
#df.describe(include='all')
Out[37]:
0 1 2 3 4 5 6
count 20.000000 20.000000 20.000000 20.000000 20.000000 20.000000 20.000000
mean 0.476465 0.558679 0.531451 0.540485 0.503293 0.501687 0.509797
std 0.332844 0.291381 0.265034 0.327511 0.313702 0.288401 0.327884
min 0.001033 0.036776 0.049275 0.053704 0.063932 0.070082 0.000725
20% 0.139959 0.307563 0.303448 0.180394 0.222371 0.184656 0.188116
30% 0.253790 0.347180 0.344175 0.268714 0.286020 0.338205 0.231742
50% 0.463660 0.606327 0.590980 0.638010 0.457199 0.514570 0.530612
70% 0.655484 0.753438 0.743569 0.782472 0.681204 0.678053 0.801718
80% 0.836773 0.884304 0.779238 0.833136 0.824564 0.727503 0.849789
90% 0.919091 0.896668 0.797928 0.893522 0.968355 0.943899 0.887775
max 0.980289 0.918078 0.872384 0.993089 0.976688 0.973893 0.926419
In [38]:
import matplotlib.pyplot as plt
import numpy as np
n = 50
# 随机产生50个0~2之间的x,y坐标
x = np.random.rand(n)*2
y = np.random.rand(n)*2
colors = np.random.rand(n) # 随机产生50个0~1之间的颜色值
area = np.pi * (10 * np.random.rand(n))**2 # 点的半径范围:0~10
# 画散点图
plt.scatter(x, y, s=area, c=colors, alpha=0.5, marker=('o'))
plt.show()

广义数据清洗

数据类型一致

提取字符串中的数字(正则)

In [39]:
import pandas as pd
import numpy as np
df = pd.DataFrame({'A':['1a',np.nan,'10a','100b','0b']})
df
Out[39]:
A
0 1a
1 NaN
2 10a
3 100b
4 0b
In [40]:
df.A.str.extract('(\d+)')
Out[40]:
0
0 1
1 NaN
2 10
3 100
4 0

字符串转时间类型

In [41]:
df = pd.DataFrame({'name':list('ABCDA'),'house':[1,1,2,3,3],'date':['2010-01-01','2010-06-09','2011-12-03','2011-04-05','2012-03-23']})
print(df.dtypes)
df.date = pd.to_datetime(df.date)
print('-'*100)
print(df.dtypes)
df
name     object
house     int64
date     object
dtype: object
----------------------------------------------------------------------------------------------------
name             object
house             int64
date     datetime64[ns]
dtype: object
Out[41]:
name house date
0 A 1 2010-01-01
1 B 1 2010-06-09
2 C 2 2011-12-03
3 D 3 2011-04-05
4 A 3 2012-03-23

统计口径一致

  • 统计报表所规定的基本指标按统一的方法进行计算
  • 统计自动化系统的每一级别上的信息处理过程都是同一的,其中每一级别都采用较高一级的分组标志进行统计指标的合并
  • 如果统计指标口径、范围不统一,统计自动化系统就无法加工和处理,也无法识别统计总体的特征

数据粒度

  • 粒度是数据仓库主要设计问题,因为它极大地影响存放在数据仓库中的数据量的大小,同时影响数据仓库所能回答的查询类型
  • 数据细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大
  • 从理论上说,数据粒度应与企业发展阶段和需求相适应
  • 从实践上说,能够采集到粒度级小的数据,就应尽可能采集粒度级小的数据

取数逻辑

用户流失率 = 总用户流失量 / 总用户量

  • 计算逻辑
    • 流失的含义是怎样的定义的?计算的期间是怎样定义的?
      • 一个月不采购?一个季度不采购?
      • 一个月不进店?一年不进店?
    • 当期新增用户数是多少?
    • 当前流失用户数是多少?其中流失用户是属于当期新增的用户数是多少?
    • 总用户流失量包含当期新增然后流失的用户吗?
    • 总用户量是期初的总用户量还是期末的总用户量?还是期初期末平均数,还是期间每日汇总而计算的平均数?

只有取数逻辑、清晰,才能保持指标涵义一致,才能进行对比分析

拆分列

  • 列的拆分是很多情况下需要的
In [42]:
df = pd.DataFrame({'name':list('ABCDA'),'house':[1,1,2,3,3],'date':['2010-01-01','2010-06-09','2011-12-03','2011-04-05','2012-03-23']})
df
Out[42]:
name house date
0 A 1 2010-01-01
1 B 1 2010-06-09
2 C 2 2011-12-03
3 D 3 2011-04-05
4 A 3 2012-03-23
In [43]:
df['year']=df['date'].map(lambda x:x.split('-')[0])
df['month']=df['date'].map(lambda x:x.split('-')[1])
df['day']=df['date'].map(lambda x:x.split('-')[2])
df
Out[43]:
name house date year month day
0 A 1 2010-01-01 2010 01 01
1 B 1 2010-06-09 2010 06 09
2 C 2 2011-12-03 2011 12 03
3 D 3 2011-04-05 2011 04 05
4 A 3 2012-03-23 2012 03 23
In [44]:
a = [['01', 'a,b,c', 5], ['02','a,b', 10], ['03', 'b,c', 20]]
data = pd.DataFrame(a, index=['user1','user2','user3'], columns=["id", "type", "num"])
data
Out[44]:
id type num
user1 01 a,b,c 5
user2 02 a,b 10
user3 03 b,c 20
In [45]:
data['type'].str.split(',')
Out[45]:
user1    [a, b, c]
user2       [a, b]
user3       [b, c]
Name: type, dtype: object
In [46]:
data['type'].str.split(',', expand=True)
Out[46]:
0 1 2
user1 a b c
user2 a b None
user3 b c None
In [47]:
data['type'].str.split(',', expand=True).stack()
Out[47]:
user1  0    a
       1    b
       2    c
user2  0    a
       1    b
user3  0    b
       1    c
dtype: object
In [48]:
data['type'].str.split(',', expand=True).stack().reset_index()
Out[48]:
level_0 level_1 0
0 user1 0 a
1 user1 1 b
2 user1 2 c
3 user2 0 a
4 user2 1 b
5 user3 0 b
6 user3 1 c
In [49]:
data_type = data['type'].str.split(',', expand=True).stack().reset_index(level = 1,drop = True)
data_type.to_frame(name='type')
Out[49]:
type
user1 a
user1 b
user1 c
user2 a
user2 b
user3 b
user3 c
In [50]:
data.drop(['type'], axis=1).join(data_type.to_frame( name = 'type1'))
Out[50]:
id num type1
user1 01 5 a
user1 01 5 b
user1 01 5 c
user2 02 10 a
user2 02 10 b
user3 03 20 b
user3 03 20 c
In [51]:
data_new = data.drop(['type'], axis=1).join(data_type.rename('type1'))
data_new
Out[51]:
id num type1
user1 01 5 a
user1 01 5 b
user1 01 5 c
user2 02 10 a
user2 02 10 b
user3 03 20 b
user3 03 20 c

保留小数位数

  • 这个是报表形式上的统一规范
In [52]:
f = 1.23456
print('%.4f' % f)
print('%.3f' % f)
print('%.2f' % f)
print(format(f, '.2f'))
print(format(f, '.3f'))
print(format(f, '.4f'))
1.2346
1.235
1.23
1.23
1.235
1.2346
  • round(x,n)函数进位规律
    • round(x,n)函数中,是否进位或四舍五入,取决于n位以及n+1位小数的值
    • 只有当n+1位数字是5的时候,容易混淆,如果n为偶数,则n+1位数是5,则进位,例如round(1.23456,3)最终变为1.235
    • 如果n为奇数,则n+1位是数5,那不进位,例如round(2.355,2),最终为2.35
    • 如果n为0,即没有填写n的时候,最终结果与上面相反,即整数部分为偶数的时候,小数位5不进位,例如(round(2.5)变为2)。
    • 整数部分为奇数的时候,小数位5进位。(round(3.5)变为4)
In [53]:
a = 1.23456
b = 2.355
c = 3.5
d = 2.5
print(round(a, 3))
print(round(b, 2))
print(round(c))
print(round(d))
1.235
2.35
4
2
In [54]:
# 保留三位小数(截断,不进位)
print(int(1.23456 * 1000) / 1000 )
1.234

数据结构--二维表行列转换

  • 行列转换的场景是非常多的,DataFrame和数据库pivot函数都能够实现

DataFrame.groupby()

In [55]:
data_new
Out[55]:
id num type1
user1 01 5 a
user1 01 5 b
user1 01 5 c
user2 02 10 a
user2 02 10 b
user3 03 20 b
user3 03 20 c
In [56]:
data_new.groupby(['id','type1'])['num'].sum().unstack()
Out[56]:
type1 a b c
id
01 5.0 5.0 5.0
02 10.0 10.0 NaN
03 NaN 20.0 20.0
In [57]:
import pandas as pd
import numpy as np
df = pd.DataFrame(np.arange(6).reshape(2,3),index=['AA','BB'],columns=['three','two','one'])
df
Out[57]:
three two one
AA 0 1 2
BB 3 4 5

DataFrame.stack()

In [58]:
df.stack().to_frame()
Out[58]:
0
AA three 0
two 1
one 2
BB three 3
two 4
one 5
In [59]:
import pandas as pd
import numpy as np
df = pd.DataFrame(np.arange(8).reshape(2,4),index=['AA','BB'],columns=[['two','two','one','one'],['A','B','C','D']])
df
Out[59]:
two one
A B C D
AA 0 1 2 3
BB 4 5 6 7
In [60]:
df.stack()
Out[60]:
one two
AA A NaN 0.0
B NaN 1.0
C 2.0 NaN
D 3.0 NaN
BB A NaN 4.0
B NaN 5.0
C 6.0 NaN
D 7.0 NaN
In [61]:
df.stack(level=1)
Out[61]:
one two
AA A NaN 0.0
B NaN 1.0
C 2.0 NaN
D 3.0 NaN
BB A NaN 4.0
B NaN 5.0
C 6.0 NaN
D 7.0 NaN
In [62]:
df.stack(level=0)
Out[62]:
A B C D
AA one NaN NaN 2.0 3.0
two 0.0 1.0 NaN NaN
BB one NaN NaN 6.0 7.0
two 4.0 5.0 NaN NaN
In [63]:
df.stack(level=[0,1]).to_frame()
Out[63]:
0
AA one C 2.0
D 3.0
two A 0.0
B 1.0
BB one C 6.0
D 7.0
two A 4.0
B 5.0
In [64]:
df
Out[64]:
two one
A B C D
AA 0 1 2 3
BB 4 5 6 7

DataFrame.unstack()

In [65]:
df.unstack().to_frame()
Out[65]:
0
two A AA 0
BB 4
B AA 1
BB 5
one C AA 2
BB 6
D AA 3
BB 7

DataFrame.melt()

  • DataFrame.melt(id_vars=None, value_vars=None, var_name=None, value_name='value', col_level=None) [source]
    • "Unpivots" DataFrame从宽格式转换为长格式,可选保留设置的标识符变量。
    • melt函数对于将DataFrame转换成这样一种格式非常有用
    • 其中一个或多个列是标识符变量(id_vars),而所有其他列(被认为是测量变量(value_vars))都"unpivoted"到行轴,只留下两个非标识符列"variable"和"value"。
In [66]:
df = pd.DataFrame(np.arange(8).reshape(2,4),index=['AA','BB'],columns=['A','B','C','D'])
df
Out[66]:
A B C D
AA 0 1 2 3
BB 4 5 6 7
In [67]:
pd.melt(df,id_vars=['A','C'],value_vars=['B','D'],var_name='B|D',value_name='(B|D)_value')
Out[67]:
A C B|D (B|D)_value
0 0 2 B 1
1 4 6 B 5
2 0 2 D 3
3 4 6 D 7

DataFrame.pivot()

In [68]:
a=[['刘玄德','男','语文',98.],['刘玄德','男','体育',60.],['关云长','男','数学',60.],['关云长','男','语文',100.]]
af=pd.DataFrame(a,columns=['name','sex','course','score'])
af
Out[68]:
name sex course score
0 刘玄德 语文 98.0
1 刘玄德 体育 60.0
2 关云长 数学 60.0
3 关云长 语文 100.0
In [69]:
af2=af.pivot('name','course','score')
af2
Out[69]:
course 体育 数学 语文
name
关云长 NaN 60.0 100.0
刘玄德 60.0 NaN 98.0

DataFrame.pivot_table()

In [70]:
af.pivot_table('score',index='name',columns='course',aggfunc='mean',margins=True,fill_value=0).reindex()
Out[70]:
course 体育 数学 语文 All
name
关云长 0 60 100 80.0
刘玄德 60 0 98 79.0
All 60 60 99 79.5
In [71]:
af.pivot_table('score',index='name',columns='course',aggfunc='sum',margins=True,fill_value=0).reset_index()
Out[71]:
course name 体育 数学 语文 All
0 关云长 0 60 100 160.0
1 刘玄德 60 0 98 158.0
2 All 60 60 198 318.0
In [72]:
af.pivot_table('score',index='name',columns='course',aggfunc='sum',margins=True,fill_value=0).to_excel('test.xls')
In [73]:
t = pd.read_excel('test.xls')
t
Out[73]:
name 体育 数学 语文 All
0 关云长 0 60 100 160
1 刘玄德 60 0 98 158
2 All 60 60 198 318

将分类变量转换为数字变量

  • 等同于将数据库表中的某列记录,使用主键和外键链接,关联到另外一张表
  • 关联列的数字类型是 int,该 int 是被关联表的主键
  • 优点:消除冗余、数据唯一

数据分组

数据离散化

  • 将连续型变量分组划分到区间。示例:
    • 将1,2,3,4,5,6,7,8,9,11,16划分到0-5,6-12,12-18三个区间
    • 数据库实现:case when then else end
In [74]:
import numpy as np
import pandas as pd

ages = np.array([1,2,3,4,5,10,40,36,12,58,62,77,89,100,18,20,25,30,32]) #年龄数据
ages
Out[74]:
array([  1,   2,   3,   4,   5,  10,  40,  36,  12,  58,  62,  77,  89,
       100,  18,  20,  25,  30,  32])
In [75]:
pd.cut(ages, 5)
Out[75]:
[(0.901, 20.8], (0.901, 20.8], (0.901, 20.8], (0.901, 20.8], (0.901, 20.8], ..., (0.901, 20.8], (0.901, 20.8], (20.8, 40.6], (20.8, 40.6], (20.8, 40.6]]
Length: 19
Categories (5, interval[float64]): [(0.901, 20.8] < (20.8, 40.6] < (40.6, 60.4] < (60.4, 80.2] < (80.2, 100.0]]
In [76]:
for i in list(pd.cut(ages, 5)):
    print(i,': ',list(pd.cut(ages, 5)).count(i))
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(20.8, 40.6] :  5
(20.8, 40.6] :  5
(0.901, 20.8] :  9
(40.6, 60.4] :  1
(60.4, 80.2] :  2
(60.4, 80.2] :  2
(80.2, 100.0] :  2
(80.2, 100.0] :  2
(0.901, 20.8] :  9
(0.901, 20.8] :  9
(20.8, 40.6] :  5
(20.8, 40.6] :  5
(20.8, 40.6] :  5
In [77]:
for i in list(set(pd.cut(ages, 5))):
    #print(i)
    print(i.left,'->',i.right)
40.6 -> 60.4
80.2 -> 100.0
0.901 -> 20.8
60.4 -> 80.2
20.8 -> 40.6

规则定义

  • 使用文字对属性进行划分。示例:
    • 根据某事件的消耗时间,将某个属性定义为“快”、“中”、“慢”
    • 数据库实现:case when then else end

数据映射(满足主数据管理)

  • 如现有系统已在使用,但主数据不统一且无法更改
  • 解决办法是在不同的主数据表之间建立数据映射关系

数据合并

pd.merge()

In [78]:
df1=pd.DataFrame({'key':['a','b','b'],'data1':range(3)})
print(df1)
df2=pd.DataFrame({'key':['a','b','c'],'data2':range(3)})
print(df2)
pd.merge(df1,df2,how='left')  
  key  data1
0   a      0
1   b      1
2   b      2
  key  data2
0   a      0
1   b      1
2   c      2
Out[78]:
key data1 data2
0 a 0 0
1 b 1 1
2 b 2 1
In [79]:
pd.merge(df1,df2,how='right')
Out[79]:
key data1 data2
0 a 0.0 0
1 b 1.0 1
2 b 2.0 1
3 c NaN 2
In [80]:
pd.merge(df1,df2,how='outer')  
Out[80]:
key data1 data2
0 a 0.0 0
1 b 1.0 1
2 b 2.0 1
3 c NaN 2
In [81]:
right=pd.DataFrame({'key1':['foo','foo','bar','bar'],'key2':['one','one','one','two'],'lval':[4,5,6,7]})
print(right)
left=pd.DataFrame({'key1':['foo','foo','bar'],'key2':['one','two','one'],'lval':[1,2,3]})
print(left)
pd.merge(left,right,on=['key1','key2'],how='outer')
  key1 key2  lval
0  foo  one     4
1  foo  one     5
2  bar  one     6
3  bar  two     7
  key1 key2  lval
0  foo  one     1
1  foo  two     2
2  bar  one     3
Out[81]:
key1 key2 lval_x lval_y
0 foo one 1.0 4.0
1 foo one 1.0 5.0
2 foo two 2.0 NaN
3 bar one 3.0 6.0
4 bar two NaN 7.0

pd.concat()

In [82]:
df1=pd.DataFrame(np.random.randn(3,4),columns=['a','b','c','d'])
print(df1)
df2=pd.DataFrame(np.random.randn(2,3),columns=['b','d','a'])
print(df2)
pd.concat([df1,df2],sort=False)
          a         b         c         d
0  0.426500  1.381156 -0.195752 -1.006517
1 -0.316466 -0.054894 -1.243819 -0.071937
2  2.384227  0.045901  0.535886 -0.454778
          b         d         a
0 -0.723292 -1.132881 -0.005357
1 -0.133029 -1.431177 -1.169933
Out[82]:
a b c d
0 0.426500 1.381156 -0.195752 -1.006517
1 -0.316466 -0.054894 -1.243819 -0.071937
2 2.384227 0.045901 0.535886 -0.454778
0 -0.005357 -0.723292 NaN -1.132881
1 -1.169933 -0.133029 NaN -1.431177
In [83]:
pd.concat([df1,df2],sort=True,ignore_index=True)
Out[83]:
a b c d
0 0.426500 1.381156 -0.195752 -1.006517
1 -0.316466 -0.054894 -1.243819 -0.071937
2 2.384227 0.045901 0.535886 -0.454778
3 -0.005357 -0.723292 NaN -1.132881
4 -1.169933 -0.133029 NaN -1.431177

pd1.join(pd2)

In [84]:
caller = pd.DataFrame({'key':['K0', 'K1', 'K2', 'K3', 'K4', 'K5'], 'A':['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
caller
Out[84]:
key A
0 K0 A0
1 K1 A1
2 K2 A2
3 K3 A3
4 K4 A4
5 K5 A5
In [85]:
other = pd.DataFrame({'key':['K0', 'K1', 'K2','K99'], 'B':['B0', 'B1', 'B2', 'B99']})
other
Out[85]:
key B
0 K0 B0
1 K1 B1
2 K2 B2
3 K99 B99
In [86]:
caller.join(other, lsuffix='_caller', rsuffix='_other')
Out[86]:
key_caller A key_other B
0 K0 A0 K0 B0
1 K1 A1 K1 B1
2 K2 A2 K2 B2
3 K3 A3 K99 B99
4 K4 A4 NaN NaN
5 K5 A5 NaN NaN

数据可视化技术

In [87]:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
In [109]:
from matplotlib.font_manager import *

# 解决中文显示问题
# 使用场景:在text()、label()、title()中添加:fontproperties=myfont1
myfont1 = FontProperties(
    fname='/usr/share/fonts/truetype/arphic/ukai.ttc'
)
# 第二种解决图例中文显示问题,配置:plt.legend(prop=myfont2)
myfont2 = matplotlib.font_manager.FontProperties(
      fname='/root/anaconda3/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/simhei.ttf'
    , size = 16
)
# 解决不显示负号的问题
matplotlib.rcParams['axes.unicode_minus'] = False
# 解决 cannot find sans-serif family 问题
matplotlib.rcParams.update({    
    'font.family':'sans-serif',
    'font.sans-serif':['Liberation Sans'],
    })

# 有时配置后需要清除一下缓存才能正确显示
# rm -rf ~/.matplotlib/*.cache
In [89]:
!rm -rf ~/.matplotlib/*.cache

饼图

In [90]:
df = pd.read_csv('second_hand_ house.csv')
df2 = df.head(10)
df2
Out[90]:
房屋编码 小区 朝向 房屋单价 参考首付 参考总价 经度 纬度
0 605093949 大望新平村 南北 5434 15.0 50.0 114.180964 22.603698
1 605768856 通宝楼 南北 3472 7.5 25.0 114.179298 22.566910
2 606815561 罗湖区罗芳村 南北 5842 15.6 52.0 114.158869 22.547223
3 605147285 兴华苑 南北 3829 10.8 36.0 114.158040 22.554343
4 606030866 京基东方都会 西南 47222 51.0 170.0 114.149243 22.554370
5 605610283 水库新村 南北 5897 13.8 46.0 114.145470 22.570187
6 601250774 水库新村 南北 8295 21.9 73.0 114.145470 22.570187
7 605525982 水库新村 南北 6145 17.7 59.0 114.145470 22.570187
8 606810540 新天地名居 51282 60.0 200.0 114.140785 22.550863
9 599540811 翠岭苑 南北 11160 30.0 100.0 114.137359 22.591920
In [91]:
df3 = df2[['小区','房屋单价']].groupby('小区').mean().reset_index().sort_values(['房屋单价'],ascending=[0])
df3
Out[91]:
小区 房屋单价
3 新天地名居 51282
0 京基东方都会 47222
6 翠岭苑 11160
4 水库新村 6779
5 罗湖区罗芳村 5842
2 大望新平村 5434
1 兴华苑 3829
7 通宝楼 3472
In [92]:
data = df3['房屋单价']        # 如果该序列值合计大于1,则会归一化
labels = df3['小区']
explode =[0,0,0,0.1,0,0,0,0]
colors = ['red','pink','magenta','yellow','orange']
plt.figure(num = 1,figsize = (8,8))
# 将横、纵坐标轴标准化处理,保证饼图是一个正圆,否则为椭圆
plt.axes(aspect='equal')                                 
# 控制X轴和Y轴的范围(用于控制饼图的圆心、半径)
plt.xlim(0,12)
plt.ylim(0,12)
#不显示边框
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
plt.gca().spines['left'].set_color('none')
plt.gca().spines['bottom'].set_color('none')
#不显示X轴、Y轴的刻度值
plt.xticks(())
plt.yticks(())
#添加图形标题
plt.title('深圳罗湖小区二手房平均单价排行榜',fontproperties=myfont2)
plt.pie(
    x=data,                                                                 # 绘制数据
    labels=labels,                                                          # 添加编程语言标签
    explode=explode,                                                        # 突出显示Python
    colors=colors,                                                          # 设置自定义填充色
    autopct='%.2f%%',                                                       # 设置百分比的格式,保留3位小数
    pctdistance=1.4,                                                        # 设置百分比标签和圆心的距离
    labeldistance=1.2,                                                      # 设置标签和圆心的距离
    startangle=120,                                                         # 设置饼图的初始角度
    center=(8,8),                                                           # 设置饼图的圆心(相当于X轴和Y轴的范围)
    radius=1,                                                               # 设置饼图的半径(相当于X轴和Y轴的范围)
    counterclock= False,                                                   # 是否为逆时针方向,False表示顺时针方向
    wedgeprops= {'linewidth':1,'edgecolor':'green'},                        # 设置饼图内外边界的属性值
    textprops= {'fontsize':12,'color':'black','fontproperties':myfont2},    # 设置文本标签的属性值
    rotatelabels = False,                                                  # 旋转每个label到指定的角度
    shadow=True,                                                            # 显示阴影
    frame=0                                                                 # 是否显示饼图的圆圈,1为显示
) 
plt.show()

散点图

In [93]:
df4 = df.head(100)[['房屋单价','经度','纬度']]
df4
Out[93]:
房屋单价 经度 纬度
0 5434 114.180964 22.603698
1 3472 114.179298 22.566910
2 5842 114.158869 22.547223
3 3829 114.158040 22.554343
4 47222 114.149243 22.554370
... ... ... ...
70 5833 114.089539 22.577080
71 5681 114.089539 22.577080
72 3571 114.083405 22.539505
73 59701 114.081795 22.531393
74 54285 114.067625 22.525508

75 rows × 3 columns

In [110]:
plt.figure(num = 1,figsize = (8,8))
plt.title('深圳罗湖小区二手房地理位置分布',fontproperties=myfont2)
plt.scatter(df4['经度'],df4['纬度'],s=df4['房屋单价']/300,c='r',alpha=0.3, linewidths=1)
plt.show()

折线图

In [95]:
df5 = df.head(100)[['朝向','房屋单价']].groupby('朝向').mean().reset_index().sort_values(['房屋单价'],ascending=[0])
df6 = df5.set_index('朝向')
df6
Out[95]:
房屋单价
朝向
东北 59701.000000
58767.750000
东西 52173.000000
52160.875000
西南 46950.000000
41621.100000
东南 33061.200000
西北 16257.666667
南北 6626.724138
In [96]:
# 设置图框的大小
fig = plt.figure(figsize = (14,6))
# 绘图
plt.plot(df5['朝向'], # x轴数据
         df5['房屋单价'], # y轴数据
         linestyle = '-', # 折线类型
         linewidth = 2, # 折线宽度
         color = 'steelblue', # 折线颜色
         marker = 'o', # 点的形状
         markersize = 6, # 点的大小
         markeredgecolor='black', # 点的边框色
         markerfacecolor='brown') # 点的填充色
# 添加标题和坐标轴标签
plt.title('房屋朝向单价折线图',fontproperties=myfont2)
plt.xlabel('朝向',fontproperties=myfont2)
plt.ylabel('房屋平均单价',fontproperties=myfont2)

# 剔除图框上边界和右边界的刻度
plt.tick_params(top = 'off', right = 'off')

# 需要将df的索引设置为中文名称,然后在此处配置为刻度标签
plt.xticks(list(df5.index),list(df6.index),fontproperties=myfont2) 

# 为了避免x轴日期刻度标签的重叠,可以设置x轴刻度自动展现,并且45度倾斜。但此处不倾斜
fig.autofmt_xdate(rotation = 0)

# 显示图形
plt.show()

柱形图

In [97]:
df5
Out[97]:
朝向 房屋单价
1 东北 59701.000000
4 58767.750000
3 东西 52173.000000
0 52160.875000
8 西南 46950.000000
5 41621.100000
2 东南 33061.200000
7 西北 16257.666667
6 南北 6626.724138
In [98]:
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
ax=df5.plot.bar(
     x='朝向'
    ,y='房屋单价'
    ,color='orange'
    ,title="房屋朝向单价柱形图"
    ,figsize=(14,6)
    ,rot=360
)
In [99]:
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
ax=df5.sort_values(['房屋单价'],ascending=[1]).plot.barh(
     x='朝向'
    ,y='房屋单价'
    ,color='orange'
    ,title="房屋朝向单价柱形图"
    ,figsize=(14,6)    
    ,stacked = True
)
In [100]:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize = (14,6))
val_ls = df5['房屋单价']
scale_ls = range(len(list(df5['朝向'])))
index_ls = df5['朝向']
plt.xticks(scale_ls,index_ls,fontproperties=myfont2)
plt.title('房屋朝向单价柱形图',fontproperties=myfont2)
plt.xlabel('朝向',fontproperties=myfont2)
plt.ylabel('房屋平均单价',fontproperties=myfont2)
plt.bar(scale_ls, val_ls,color='orange')
fig.autofmt_xdate(rotation = 0)
#plt.tight_layout()
plt.show()

直方图

  • 直方图是一个可以快速展示数据概率分布的工具,直观易于理解
In [101]:
df7 = df[['朝向','房屋单价']]
df7
Out[101]:
朝向 房屋单价
0 南北 5434
1 南北 3472
2 南北 5842
3 南北 3829
4 西南 47222
... ... ...
70 5833
71 5681
72 南北 3571
73 东北 59701
74 54285

75 rows × 2 columns

In [102]:
x=np.array(list(df7['房屋单价']))
x
Out[102]:
array([ 5434,  3472,  5842,  3829, 47222,  5897,  8295,  6145, 51282,
       11160, 38557, 45833, 11891, 51923, 62500, 52631, 38888, 43023,
       95238, 80000, 66574, 66574,  8928,  5714, 52173, 61000, 48725,
        3835,  3958,  3835, 48888, 63879, 89523, 64516, 66000,  5058,
        4545, 55714,  9911,  9693,  9693,  8363,  9552, 41666, 50000,
       50000,  5113,  4772,  4545,  5842, 38000,  4777,  6588, 36923,
        5582, 47878, 47878,  5370, 41818, 48387, 30411, 55806, 26388,
        3960,  6052, 41578,  4444,  4245,  4128,  3773,  5833,  5681,
        3571, 59701, 54285])
In [103]:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize = (18,6))
x=np.array(list(df7['房屋单价']))
# 设置连续的边界值
bins=np.arange(0,100000,2500)
# 柱状图的宽度
width=2500
# 直方图会进行统计各个区间的数值
frequency_each,_,_= plt.hist(x,bins,color='red',width=width,alpha=1)
# 利用返回值来绘制区间中点连线
# 回值的bins的数据长度比频数的长度大1,
# 使用从1开始直到bins结束,即将第0个元素去掉,保证二者的长度一致,
# 再减去width的一半,保证在中点(CodeFUN)
plt.plot(bins[1:]-(width//2),frequency_each,color='red')

# 直方图会进行统计各个区间的数值
plt.hist(x,bins,color='blue',alpha=0.75)#alpha设置透明度,0为完全透明
plt.xlabel('scores')
plt.ylabel('count')
plt.grid()
plt.xlim(0,76)# 设置x轴分布范围
plt.xticks(np.arange(min(df7['房屋单价']), max(df7['房屋单价'])+6000, 3000))
plt.show()
  • 备注:
    • 房屋单价主要集中在3500元-6500元之间,其次是9000元左右
    • 超过2万的房屋单价,主要集中在4.8万元-5.2万元,其次是6.5万左右
In [104]:
a=df7.plot.hist(
     x='朝向'
    ,y='房屋单价'
    ,color='blue'
    ,title="房屋朝向单价折线图"
    ,figsize=(18,6)
    ,grid=True
    ,legend=True
    ,xlim=(min(df7['房屋单价'])-3000, max(df7['房屋单价'])+3000)
    ,xticks=np.arange(min(df7['房屋单价']), max(df7['房屋单价']), 3000)
    ,bins=np.arange(0,100000,2500)
)

树状图

  • 聚类分析(clustering analysis)是将一组对象根据其特征分成不同的 cluster,使得同一 cluster 内的对象在某种意义上比不同的 cluster 之间的对象更为相似。
  • 树形图 (dendrogram)可以用来直观地表示层次聚类的成果
In [105]:
from scipy.cluster.hierarchy import dendrogram, linkage,fcluster
from matplotlib import pyplot as plt
X = [[i] for i in [2, 8, 0, 4, 1, 9, 9, 0]]
Z = linkage(X, 'single')
f = fcluster(Z,4,'distance')
fig = plt.figure(figsize = (12,6))
dn = dendrogram(Z)
plt.show()
  • 步骤说明
    1. 目标:将“2, 8, 0, 4, 1, 9, 9, 0”分类
    2. 上述数字的索引分别对应为:
索引号 数字
0 2
1 8
2 0
3 4
4 1
5 9
6 9
7 0
    1. 从树形图得到结论:
      1. 索引5、6(代表数字9、9)为一类
      2. 索引1、5、6(8、9、9)为更高一个层级的一类
      3. 索引2、7(0、0)为一类
      4. 索引2、7、0、4(0、0、2、1)为更高一个层级的一类
      5. 索引3、2、7、0、4(4、0、0、2、1)为再更高一个层级的一类
      6. 以此类推,得到一个完整的分层聚类结果
    2. scipy.cluster.hierarchy的linkage函数实现的聚类分析原理后续说明

地图

In [111]:
from pyecharts.charts import Map,Geo
from pyecharts import options as opts
data=[
     ("广东",10430.03)
    ,("山东",9579.31)
    ,("河南",9402.36)
    ,("四川",8041.82)
    ,("江苏",7865.99)
    ,("河北",7185.42)
    ,("湖南",6568.37)
    ,("安徽",5950.1)
    ,("浙江",5442)
    ,("湖北",5723.77)
    ,("广西",4602.66)
    ,("云南",4596.6)
    ,("江西",4456.74)
    ,("辽宁",4374.63)
    ,("黑龙江",3831.22)
    ,("陕西",3732.74)
    ,("山西",3571.21)
    ,("福建",3552)
    ,("重庆",2884)
    ,("贵州",3476.65)
    ,("吉林",2746.22)
    ,("甘肃",2557.53)
    ,("内蒙古",2470.63)
    ,("上海",2301.391)
    ,("台湾",2316.2)
    ,("新疆",2181.33)
    ,("北京",1961.2)
    ,("天津",1293.82)
    ,("海南",867.15)
    ,("香港",709.76)
    ,("青海",562.67)
    ,("宁夏",630.14)
    ,("西藏",300.21)
    ,("澳门",55.23)
]
map=(
    Map()
    .add("",data,"china")
    .set_global_opts(
        title_opts=opts.TitleOpts(title="各省市人口数",subtitle="数据来源:中国统计年鉴(万人)",pos_right="center",pos_top="5%"),
        visualmap_opts=opts.VisualMapOpts(max_=12000),
    )  
)
map.render_notebook()
Out[111]:

热力图

In [107]:
from pyecharts import options as opts 
from pyecharts.charts import Geo 
from pyecharts.globals import GeoType #Geo图的类型
data=[ 
     ("北京","2171")
    ,("天津","1557")
    ,("河北","7520")
    ,("内蒙古","2529")
    ,("辽宁","4369")
    ,("吉林","2717")
    ,("黑龙江","3789")
    ,("上海","2418")
    ,("江苏","8029")
    ,("浙江","5657")
    ,("安徽","6255")
    ,("福建","3911")
    ,("江西","4622")
    ,("山东","10006")
    ,("河南","9559")
    ,("湖北","5902")
    ,("湖南","6860")
    ,("广东","11169")
    ,("广西","4885")
    ,("海南","926")
    ,("重庆","3075")
    ,("四川","8302")
    ,("贵州","3580")
    ,("云南","4801")
    ,("西藏","337")
    ,("陕西","3835")
    ,("甘肃","2626")
    ,("青海","598")
    ,("宁夏","682")
    ,("新疆","2445"),
]
pop_geo = ( 
    Geo() .add_schema(maptype="china") 
    .add("",data,type_=GeoType.HEATMAP) #热度效果HEATMAP
    .set_series_opts(label_opts=opts.LabelOpts(is_show=False))  
    .set_global_opts(title_opts=opts.TitleOpts(
        title="全国年度人口数"
        ,subtitle="数据来源:中国统计年鉴(万人)"
        ,pos_right="center"
        ,pos_top="5%"
    )
    ,visualmap_opts=opts.VisualMapOpts(max_=11169,pos_left="8%",is_piecewise=True))
)
pop_geo.render_notebook()
Out[107]:

其他动态图

In [108]:
from pyecharts import options as opts 
from pyecharts.charts import Geo # 地理坐标系绘制方法
from pyecharts.globals import GeoType,ThemeType,SymbolType #Geo图的类型 主题 涟漪图形符号
data=[("四川","8302"),("山东","10006"),("河南","9559"),("湖南","6860"),("广东","11169")]
geo = (
    Geo(init_opts=opts.InitOpts(width="600px",height="500px",theme=ThemeType.DARK))  
    .add_schema(maptype="china",
                #itemstyle_opts=opts.ItemStyleOpts(color='white',border_color="#EE2C2C")#地图背景色
                #zoom=1.5,
                #is_roam=True,
                #center=Geo().get_coordinate('广东'),  # 视角中心
                emphasis_itemstyle_opts=opts.ItemStyleOpts(color="#31708f"),  # 高亮颜色
                # emphasis_label_opts=opts.ItemStyleOpts(color="white", )  # 字体颜色
               )
    
    .add("",
         data,
         type_=GeoType.EFFECT_SCATTER,
         symbol_size=6
        )
    
    .add("",
         [("广东","四川"),("广东","山东"),("广东","湖南"),("广东","河南")],
         type_=GeoType.LINES, 
         effect_opts=opts.EffectOpts(symbol=SymbolType.ARROW,symbol_size=6,color='#5f99bb'),
         linestyle_opts=opts.LineStyleOpts(curve=0.2,color="#B0E2FF"))
    .set_series_opts(label_opts=opts.LabelOpts(is_show=False))  
    .set_global_opts(title_opts=opts.TitleOpts(title="广东出发的航班",subtitle="数据来源:中国民航局官方网站",pos_right="center",pos_top="5%")) 
)
geo.render_notebook()
Out[108]:

爱与彼岸财经分析团队编制

爱与彼岸财经分析