一、数据清洗
1、检测与处理异常值
异常值是指数据中个别值的数值明显偏离其余的数值,有时也称为离群点,检测异常值 就是检验数据中是否有录入错误以及是否含有不合理的数据
异常值的存在对数据分析十分危险,如果计算分析过程的数据有异常值,那么会对结果 会产生不良影响,从而导致分析结果产生偏差乃至错误
比如在分析银行欺诈案例时,核心就是要发现异常值,这个时候异常值对我们是有用的,再比如,在统计某个城市的平均收入的时候,有人月收入是好几个亿,这个时候这个人就是 一个异常值,这个异常值会拉高城市的整体平均收入,因此可能会得到一个不真实的分析结果
常用的异常值检测主要为具体业务法、3σ原则和箱线图分析三种方法
(1)、具体业务法
根据你对业务的理解,然后对每一个指标设定一个合理的范围,一旦超过这个范围,则认为是异常值
比如收入,一般来说都是正数,如果出现小于 0,则认为是异常值
再比如年龄,正常的年龄可能在 100 以内,如果出现年龄是好几百的,那么也认为是异常值
代码实现:
# 异常值:远离正常范围的错误的值 ---剔除
# 离群值:远离正常范围的正确的值
# 判定并剔除
# 1、具体业务法
# 根据自己对具体业务的理解,设定阈值,再去进行判断
# 以初中生的年龄为例 --->[12,16]
def juti_yewu(data):
"""
根据具体业务来剔除异常值
:param data: 需要剔除异常值的数据series
:return: bool数组
"""
# 确定上限
up = 16
# 确定下限
low = 12
# 判断
mask1 = data <= up # 与上限进行对比
mask2 = data >= low # 与下限进行对比
# 求 & --->and
mask = mask1 & mask2
return mask
#
# # 创建df
df = pd.DataFrame(data={
'name': ['zs', 'ls', 'ww', 'zl'],
'age': [12, 16, 18, 9]
})
print('df:\n', df)
# # 调用
mask = juti_yewu(df['age'])
print('mask:\n', mask)
# # 筛选
df = df.loc[mask, :]
print('剔除异常值之后的结果为:\n', df)
(2)、3σ原则
3σ原则又称为拉依达法则。该法则就是先假设一组检测数据只含有随机误差,对原始 数据进行计算处理得到标准差,然后按一定的概率确定一个区间,认为误差超过这个区间的就属于异常值
这种判别处理方法仅适用于对正态或近似正态分布的样本数据进行处理,如下表所示, 其中σ代表标准差,μ代表均值,x=μ为图形的对称轴
数据的数值分布几乎全部集中在区间(μ-3σ,μ+3σ)内,超出这个范围的数据仅占不 到 0.3%,故根据小概率原理,可以认为超出 3σ的部分数据为异常数据
正态分布的数据分布表
数值分布 | 在数据中的占比 |
---|---|
(μ-σ,μ+σ) | 0.6827 |
(μ-2σ,μ+2σ) | 0.9545 |
(μ-3σ,μ+3σ) | 0.9973 |
代码实现:
# 2、3sigma原则
# 正态分布的 推论 ---> 99.73%的数据 (u-3a,u+3a)之间 ,拆过这个区间剩下的0.27%为异常值
def three_sigma(data):
"""
基于3sigma原则进行剔除异常值
:param data: 需要剔除异常值的数据series
:return: bool数组
"""
# 确定上限
# u + 3a
up = data.mean() + 3 * data.std()
# 确定下限
low = data.mean() - 3 * data.std()
# 比较
mask1 = data < up
mask2 = data > low
# 求 &
mask = mask1 & mask2
return mask
(3)、箱线图分析
箱型图提供了识别异常值的一个标准,即异常值通常被定义为小于 QL-1.5IQR 或大于 QU+1.5IQR 的值
QL 称为下四分位数,表示全部观察值中有四分之一的数据取值比它小
QU 称为上四分位数,表示全部观察值中有四分之一的数据取值比它大
IQR 称为四分位数间距,是上四分位数 QU 与下四分位数 QL 之差,其间包含了全部观察值的一半
箱线图依据实际数据绘制,真实、直观地表现出了数据分布的本来面貌,且没有对数据 做任何限制性要求,其判断异常值的标准以四分位数和四分位数间距为基础
四分位数给出了数据分布的中心、散布和形状的某种指示,具有一定的鲁棒性,即 25% 的数据可以变得任意远而不会很大地扰动四分位数,所以异常值通常不能对这个标准施加影响,鉴于此,箱线图识别异常值的结果比较客观,因此在识别异常值方面具有一定的优越性
代码实现:
# 3、箱线分析法
# 箱线图 ---四分位数指标
# 正常值的范围在 (ql-1.5iqr,qu+1.5iqr)----> 超过该区间则可认为为异常值
# ql --->下四分位数
# qu --->上四分位数
# iqr ---->四分位间距 ---> qu -ql
def box_analysis(data):
"""
基于箱线分析法来剔除异常值
:param data: 需要剔除异常值的数据series
:return: bool数组
"""
# 确定qu
qu = data.quantile(q=0.75)
# 确定ql
ql = data.quantile(q=0.25)
# 确定iqr
iqr = qu - ql
# 确定上限
up = qu + 1.5 * iqr
# 确定下限
low = ql - 1.5 * iqr
# 比较
mask1 = data < up
mask2 = data > low
# 求 &
mask = mask1 & mask2
return mask
二、案例:医院数据分析
近些年来,随着人民生活的提升,人民群众对于医疗条件的选择尤为重视,为此,各个诊所、医院应运而生来保障人民的身体健康
以下为某医院售药部分的售药记录情况,请根据该医院的售药记录数据,进行相关数据 处理,并提出问题,挖掘其中有价值的信息,为该医院的下一步营销进行指导
某医院 2018 年数据(部分)
购药时间 |社保卡号| 商品编码| 商品名称| 销售数量| 应收金额 |实收金额
|:–😐:–😐:–😐:–😐:–😐:–😐
2018-01-01| 星期五| 001616528236701| 强力 VC银翘片| 6| 82.8 |69
2018-01-02| 星期六| 001616528236701| 清热解毒口服液| 1 |28| 24.64
2018-01-06| 星期三| 0012602828236701| 感康| 2 |16.8| 15
2018-01-11| 星期一| 0010070343428236701| 三九感冒灵 |1| 28| 28
2018-01-15| 星期五| 00101554328236701| 三九感冒灵| 8| 224 |208
2018-01-20| 星期三| 0013389528236701| 三九感冒灵| 1 |28| 28
先进行数据清洗:
(1)把列索引变为容易理解的索引值
(2)删掉有空值的数据
(3)将’销售数量’, ‘应收金额’, '实收金额’数据类型转换为 int
(4)删除’销售数量’, ‘应收金额’, '实收金额’中小于 0 的数据
(5)'成交时间’特征转标准时间格式:
再进行分析:
(1) 每个月的人流量?
(2)人均平均消费?
(3)使用最频繁的前十种药品?
(4)多少人办社保卡?
代码实现:
import pandas as pd
# 让标题和内容对齐
pd.set_option('display.unicode.east_asian_width',True)
# 加载数据
data = pd.read_excel('./某医院2018年数据.xlsx')
print('data:\n', data)
print('data:\n', data.columns)
# 数据处理
# (1)把列索引变为容易理解的索引值
# 将 购药时间 ---> 成交时间
data.rename(columns={
'购药时间': '成交时间'}, inplace=True)
print('修改之后的列索引:\n', data.columns)
# (2)删掉有空值的数据
# 检测缺失值
print('缺失值检测结果为:\n', pd.isnull(data).sum())
# 删除含有缺失值的行
data.dropna(axis=0, how='any', inplace=True)
print('删除缺失值之后的结果为:\n', data)
# (3)将'销售数量', '应收金额', '实收金额'数据类型转换为 int
# 修改类型 ---astype
data.loc[:, ['销售数量', '应收金额', '实收金额']] = data.loc[:, ['销售数量', '应收金额', '实收金额']].astype('int')
print('data的元素的数据类型:\n', data.dtypes)
# (4)删除'销售数量', '应收金额', '实收金额'中小于 0 的数据
# 删除法
# 确定bool数组
mask1 = data.loc[:, '销售数量'] < 0
mask2 = data.loc[:, '应收金额'] < 0
mask3 = data.loc[:, '实收金额'] < 0
# 确定 最终的bool数组
mask = mask1 | mask2 | mask3
# 确定要删除的行的名称
drop_labels = data.loc[mask, :].index
# 删除
data.drop(labels=drop_labels, axis=0,<