【从零开始学python数据分析与挖掘 刘顺祥著】
文章目录
pandas
序列与数据库的构造
构建序列
构造一个序列可以使用四种方式实现:
- 通过同质的
列表或元组
构建。 - 通过
字典
构建。 - 通过
Numpy中的一维数组
构建。 - 通过数据框
DataFrame中的某一列
构建。
import numpy as np
import pandas as pd
# 列表
s1 = pd.Series([2.8, 3.01, 8.99, 8.59, 5.18])
# 元组
s11 = pd.Series((2.8, 3.01, 8.99, 8.59, 5.18))
# 字典
s2 = pd.Series({'北京': 2.8, '上海': 3.01, '广东': 8.99, '江苏': 8.59, '浙江': 5.18})
# numpy的一维数组
s3 = pd.Series(np.arange(5))
# DataFrame中的某一列
df2 = pd.DataFrame({'姓名': ['张三', '李四', '王五'], '年龄': [23, 27, 26], '性别': ['男', '女', '男']})
s4 = pd.Series(df2['姓名'])
结果:
0 2.80
1 3.01
2 8.99
3 8.59
4 5.18
dtype: float64
******************************
北京 2.80
上海 3.01
广东 8.99
江苏 8.59
浙江 5.18
dtype: float64
******************************
不管是列表、元组还是一维数组
,构造的序列结果都是第一个打印的样式。该样式会产生两列,第一列属于序列的行索引(可以理解为行号),自动从0开始,第二列才是序列的实际值。
通过字典
构造的序列就是第二个打印样式,仍然包含两列,所不同的是第一列不再是行号,而是具体的行名称(label),对应到字典中的键,第二列是序列的实际 值,对应到字典中的值。
选取序列中的元素
# 取出序列的元素
print(s1[[0,3,4]])
print(s2[['北京','江苏']])
print(s2[[0, 3]])
# 通过numpy数学函数
print(np.log(s1))
print(np.mean(s1))
# 通过序列的方法
print(s1.mean())
如果序列是行名称风格
,既可以使用位置(行号)索引
,又 可以使用标签(行名称)索引
;
如果需要对序列进行数学函数
的运算,一般首选numpy模块,因为Pandas模块在这方面比较缺乏;
如果是对序列做统计运算
,既可以使用numpy模块中的函数,也可以使用序列的“方法”,作者一般首选序列的“方法”,因为序列的“方法”更加丰富,如计算序列的偏度、峰度等,而Numpy是没有这样的函数的。
构造数据框
数据框实质上就是一个数据集,数据集的行代表每一条观测,数据集的列则代表各个变量。在一个数据框中可以存放不同数据类型的序列,如整数型、浮点型、字符型和日期时间型,而数组和序列则没有这样的优势,因为它们只能存放同质数据。构造一个数据框可以应用如下方式:
- 通过
嵌套的列表或元组
构造。 - 通过
字典
构造。 - 通过
二维数组
构造。 - 通过
外部数据的读取
构造。
# 列表
df1 = pd.DataFrame([['张三',23,'男'],['李四',27,'女'],['王五',26,'女']v])
# 字典
df2 = pd.DataFrame({'姓名':['张三','李四','王五'],'年龄':[23,27,26],'性别':['男','女','男']})
# numpy二维数组
df3 = pd.DataFrame(np.array([['张三',23,'男'],['李四',27,'女'],['王五',26,'女']]))
结果:
0 1 2
0 张三 23 男
1 李四 27 女
2 王五 26 女
******************************
姓名 年龄 性别
0 张三 23 男
1 李四 27 女
2 王五 26 男
选取数据框的元素
df2 = pd.DataFrame({'姓名': ['张三', '李四', '王五'], '年龄': [23, 27, 26], '性别': ['男', '女', '男']})
print(df2)
# 方法一:df[columns list],输入列名
print(df2[['年龄', '性别']])
# 方法二:df.loc[],用label(行名或列名)做索引
print(df2.loc[1:3, ['年龄', '性别']]) # 选择[1,3)行的年龄和性别属性列
# 方法三:df.iloc[],行或列的整数位置
print(df2.iloc[1:3,1:3])
外部数据的读取
文本文件的读取
txt文件:
数据来源:某公司人事记录表
时间范围:2017.1.1~2017.6.30
year,month,day,gender,occupation,income
1990,3,7,男,销售经理,6&000
1989,8,10,女,化妆师,8&500
# 1991,10,10,男,后端开发,13&500
1992,10,7,女,前端设计,6&500
1985,6,15,男,数据分析师,18&000
该数据集仅用作参考!
不可以用于他用!
备注于2018年2月。
所呈现的txt格式数据集存在一些常见的问题,具体如下:
- 数据集并不是从第一行开始,前面几行实际上是数据集的来源说明,读取数据时需要
注意什么问题。 - 数据集的末尾3行仍然不是需要读入的数据,如何避免后3行数据的读入。
- 中间部分的数据,第四行前加了#号,表示不需要读取该行,该如何处理。
- 数据集中的收入一列,千分位符是&,如何将该字段读入为正常的数值型数据。
- 如果需要将year、month和day三个字段解析为新的birthday字段,该如何做到。
- 数据集中含有中文,一般在读取含中文的文本文件时都会出现编码错误,该如何解决。
import numpy as np
import pandas as pd
user_income = pd.read_table(filepath_or_buffer=r'./data_test01.txt',
sep=',',
parse_dates={'birthday': [0, 1, 2]},
skiprows=3,
skipfooter=4,
comment='#',
encoding='utf-8',
thousands='&',
engine='python'
)
print(user_income)
结果:
birthday gender occupation income
0 1990-03-07 男 销售经理 6000
1 1989-08-10 女 化妆师 8500
2 1992-10-07 女 前端设计 6500
3 1985-06-15 男 数据分析师 18000
代码分析:
- 由于read_table函数在读取数据时,默认将字段分隔符
sep
设置为Tab制表符,而原始数据集是用逗号分割每一列,所以需要改变sep参数; parse_dates
参数通过字典实现前三列的日期解析,并合并为新字段birthday;skiprows
和skipfooter
参数分别实现原数据集开头几行和末尾几行数据的跳过;- 由于数据部分的第四行前面加了#号,因此通过
comment
参数指定跳过的特殊行; - 这里仅改变字符编码参数
encoding
是不够的,还需要将原始的txt文件另存为UTF-8格式; - 对于收入一列,由于千分位符为&,因此为了保证数值型数据的正常读入,需要设置
thousands
参数为&。
电子表格的读取
该数据集反映的是儿童类服装的产品信息。在读取数据时需要注意两点:
一是该表没有表头,如何读数据的同时就设置好具体的表头;
另一点是数据集的第一列实际上是字符型的字段,如何避免数据读入时自动变成数值型字段。
00101 | 儿童裤 | 黑色 | 109 |
---|---|---|---|
01123 | 儿童上衣 | 红色 | 229 |
01010 | 儿童鞋 | 蓝色 | 199 |
00100 | 儿童内衣 | 灰色 | 159 |
info = pd.read_excel(
io='./data_test02.xlsx',
names=['编号', '类型', '颜色', '价格'],
converters={0: str}
)
print(info)
编号 类型 颜色 价格
0 01123 儿童上衣 红色 229
1 01010 儿童鞋 蓝色 199
2 00100 儿童内衣 灰色 159
这里需要重点说明的是converters参数
,通过该参数可以指定某些变量需要转换的函数。 很显然,原始数据集中的商品ID是字符型的,如果不将该参数设置为{0:str}
,读入的数据与原始的数据集就不一致了。
数据库数据的读取
以mysql
为例,安装pip install pymysql
。
pymysql中的connect
简单使用
import pymysql
# 连接Mysql数据库
conn = pymysql.connect(
host='localhost',
port=3306,
database='fanle',
user='root',
password='root',
# 如果报错'NoneType' object has no attribute 'encoding',则将utf-8修改为utf8
charset='utf8'
)
# 读取数据
user = pd.read_sql('select * from user;',conn)
# 数据输出
print(user)
# 关闭连接
conn.close()
由于MySQL的原数据集中含有中文,为了避免乱码的现象,将connect函数中的chartset参数设置为utf8
。
读取数据时,需要用到Pandas模块中的read_sql函数
,该函数至少传入两个参数,一个是读取数据的查询语句(sql),另一个是连接桥梁(con)
在读取完数据之后,请务必关闭连接conn,因为它会一直占用电脑的资源,影响电脑的运行效率。
数据类型转换及描述统计
读入二手车信息:
import numpy as np
import pandas as pd
# 读取数据
cars = pd.read_csv(filepath_or_buffer='./sec_cars.csv', sep=',')
# 预览数据的前5行
print(cars.head())
# 预览数据的后5行
print(cars.tail())
# 查看数据集的行列数
print(cars.shape)
# 查看数据集每个变量的数据类型
print(cars.dtypes)
结果:
Brand Name ... Sec_price New_price
0 众泰 众泰T600 2016款 1.5T 手动 豪华型 ... 6.8 9.42万
1 众泰 众泰Z700 2016款 1.8T 手动 典雅型 ... 8.8 11.92万
2 众泰 大迈X5 2015款 1.5T 手动 豪华型 ... 5.8 8.56万
3 众泰 众泰T600 2017款 1.5T 手动 精英贺岁版 ... 6.2 8.66万
4 众泰 众泰T600 2016款 1.5T 手动 旗舰型 ... 7.0 11.59万
[5 rows x 7 columns]
Brand Name ... Sec_price New_price
11120 DS DS 3 2012款 1.6L 手自一体 风尚版 ... 10.80 23.86万
11121 DS DS 3 2012款 1.6L 手自一体 风尚版 ... 9.50 23.86万
11122 DS DS 5LS 2015款 1.6T 手自一体 THP160雅致版 ... 12.30 20.28万
11123 DS DS 5 2015款 1.8T 手自一体 THP200 豪华版 ... 17.99 28.65万
11124 DS DS 6 2016款 1.6T 豪华版THP160 ... 16.00 24.95万
[5 rows x 7 columns]
(11125, 7)
Brand object
Name object
Boarding_time object
Km(W) float64
Discharge object
Sec_price float64
New_price object
dtype: object
结果如上,该数据集一共包含了11125条记录和7个变量,除二手车价格Sec_price和行驶里程数Km(W)为浮点型数据之外,其他变量均为字符型变量。
但是,从表5-5来看,二手车的上牌时间Boarding_time应该为日期型,新车价格New_price应该为浮点型,为了后面的数据分析,需要对这两个变量进行类型的转换,具体操作如下:
原文中:
# 修改二手车上牌时间的数据类型
cars.Boarding_time = pd.to_datetime(cars.Boarding_time, format='%Y年%m月')
# 修改二手车新车价格的数据类型
cars.New_price = cars.New_price.str[:-1].astype('float')
# 重新查看各变量的数据类型
print(cars.dtypes)
但是数据集中有一些行的New_price列
与其他不一样:
按照上述方法会报错:类型转换错误could not convert string to float: '暂'
故需要对这些行特殊处理,将其选出来后从数据框中删掉。
# 修改二手车上牌时间的数据类型
cars.Boarding_time = pd.to_datetime(cars.Boarding_time, format='%Y年%m月')
# 修改二手车新车价格的数据类型
cars.drop(cars[cars.New_price == '暂无'].index, inplace=True)
cars.New_price = cars.New_price.str[:-1].astype('float')
# 重新查看各变量的数据类型
print(cars.dtypes)
解析:
**cars.New_price == ‘暂无’:**每一行的布尔值
**cars[cars.New_price == ‘暂无’]:**返回为true的那些行。
**cars[cars.New_price == ‘暂无’].index:**返回那些行的下标。
结果:
Brand object
Name object
Boarding_time datetime64[ns]
Km(W) float64
Discharge object
Sec_price float64
New_price float64
dtype: object
如上结果所示,经过两行代码的处理,上牌时间Boarding_time
更改为了日期型数据
,新车价格New_price
更改为了浮点型数据
。
Pandas模块中的to_datetime函数
可以通过format参数
灵活地将各种格式的字符型日期转换成真正的日期数据;
由于二手车新车价格含有“万”字
,因此不能直接转换数据类型,为达到目的,需要三步走,首先通过str方法将该字段转换成字符串,然后通过切片手段,将“万”字剔除,最后运用astype方法,实现数据类型的转换。
- 数值型变量的描述性统计
关于数据的描述性分析可以使用describe方法
:
# 数值型变量的描述性统计
print(cars.describe())
结果:
Km(W) Sec_price New_price
count 10984.000000 10984.000000 10984.000000
mean 6.266357 25.652192 51.326006
std 3.480678 52.770268 79.682066
min 0.020000 0.650000 2.910000
25% 4.000000 5.200000 16.050000
50% 6.000000 10.200000 26.690000
75% 8.200000 23.800000 52.210000
max 34.600000 808.000000 976.920000
以上都是有关数据的统计描述,但并不能清晰地知道数据的形状分布,如数据是否有偏以及是否属于“尖峰厚尾”的特征,为了一次性统计数值型变量的偏度和峰度,可以参考如下代码:
# 选出所有数值型变量
num_val = cars.columns[cars.dtypes != 'object'][1:]
# print(cars[num_val])
# 计算偏度和峰度
def skew_kurt(x):
skewness = x.skew()
kurtsis = x.kurt()
return pd.Series(data=[skewness, kurtsis], index=['Skew', 'Kurt'])
print(cars[num_val].apply(func=skew_kurt, axis=0))
结果:
Km(W) Sec_price New_price
Skew 0.829915 6.313738 4.996912
Kurt 2.406258 55.381915 33.519911
如上结果所示正是每个数值型变量的偏度
和峰度
,这三个变量都属于右偏(因为偏度值均大于0),而且三个变量也是尖峰的(因为峰度值也都大于0)。
代码说明:columns方法
用于返回数据集的所有变量名,通过布尔索引和切片方法获得所有的数值型变量;
在自定义函数中,运用到了计算偏度的skew方法和计算峰度的kurt方法,然后将计算结果组合到序列中;
最后使用apply方法
,该方法的目的就是对指定轴(axis=0,即垂直方向的各列)进行统计运算(运算函数
即自定义函数)。
- 离散型变量的统计描述
# 离散型变量的统计描述
print(cars.describe(include=['object']))
结果:
Brand Name Discharge
count 10984 10984 10984
unique 104 4374 33
top 别克 经典全顺 2010款 柴油 短轴 多功能 中顶 6座 国4
freq 1346 126 4262
如上结果包含离散变量的四个统计值,分别是非缺失观测数
、唯一水平数
、频次最高的离散值
和具体的频次
。
如果要统计各个离散值的频次(以标准排量Discharge为例)
# 各个排量的频次统计
freq = cars.Discharge.value_counts()
print(freq)
# 各个排量的频次除以总数
freq_ratio = freq / cars.shape[0]
freq_df = pd.DataFrame({'freq': freq, 'freq_ratio': freq_ratio})
print(freq_df.head())
结果:
freq freq_ratio
国4 4262 0.388019
欧4 1848 0.168245
欧5 1131 0.102968
国4,国5 843 0.076748
国3 772 0.070284
如果需要把行标签设置为数据框中的列
,可以使用reset_index方法
,具体操作如下:
reset_index方法
的使用还是比较频繁的,它可以非常方便地将行标签转换为数据框的变量。
# 将行索引设为变量
freq_df.reset_index(inplace=True)
print(freq_df.head())
结果:
index freq freq_ratio
0 国4 4262 0.388019
1 欧4 1848 0.168245
2 欧5 1131 0.102968
3 国4,国5 843 0.076748
4 国3 772 0.070284
字符与日期数据的处理
待处理的数据表:
需要做的修改:
- 更改出生日期birthday和手机号tel两个字段的数据类型。
- 根据出生日期birthday和开始工作日期start_work两个字段新增年龄和工龄两个字
段。 - 将手机号tel的中间四位隐藏起来。
- 根据邮箱信息新增邮箱域名字段。
- 如何基于other字段取出每个人员的专业信息。
import pandas as pd
import datetime
df = pd.read_excel(
io='./data_test03.xlsx'
)
# print(df.dtypes)
# print('*' * 30)
# 1.1更改birthday的数据类型
df.birthday = pd.to_datetime(df.birthday, format='%Y/%m/%d')
# 1.2更改tel的数据类型
df.tel = df.tel.astype('str')
# 2.新增年龄和工龄两列
df['age'] = datetime.datetime.today().year - df.birthday.dt.year
df['workage'] = datetime.datetime.today().year - df.start_work.dt.year
# 3.将手机号中间四位隐藏起来
df.tel = df.tel.apply(func=lambda x: x.replace(x[3:7], '****'))
# 4.取出邮箱的域名
df['email_domain'] = df.email.apply(func=lambda x: x.split('@')[1])
# 5.取出人员的专业信息
df['profession'] = df.other.str.findall('专业:(.*?),')
# print(df.other.str.findall('专业:(.*?),'))
# 6.取出birthday、start_work和other变量
df.drop(['birthday', 'start_work', 'other'], axis=1, inplace=True)
print(df.dtypes)
print(df)
Series对象
和DataFrame的列数据
提供了cat、dt、str三种属性接口(accessors),分别对应分类数据、日期时间数据和字符串数据,通过这几个接口可以快速实现特定的功能,非常快捷。
- 从
other变量
中获取人员的专业信息,该问题的解决使用了字符串的正则表达式,不管是字符串“方法”还是字符串正则,在使用前都需要对变量使用一次str方法。由于findall返回的是列表值,因此衍生出的email_domain字段值都是列表类型。
结果:
0 [电子商务]
1 [汽修]
2 [数学]
3 [统计学]
4 [美术]
5 [化学]
6 [物理]
7 [政治学]
- 如果需要
删除数据集中的某些变量
,可以使用数据框的drop方法
。该方法接受的第一个参数,就是被删除的变量列表,尤其要注意的是,需要将axis参数设置为1,因为默认drop方法是用来删除数据框中的行记录。
常用的数据清洗方法
在数据处理过程中,一般都需要进行数据的清洗工作,如数据集是否存在重复、是否存在缺失、数据是否具有完整性和一致性、数据中是否存在异常值等。当发现数据中存在如上可能的问题时,都需要有针对性地处理。
重复观测处理
重复观测,顾名思义是指观测行存在重复的现象,重复观测的存在会影响数据分析和挖掘结果的准确性,所以在数据分析和建模之前需要进行观测的重复性检验,如果存在重复观测,还需要进行重复项的删除。
待处理数据集:
df = pd.read_excel(
io='./data_test04.xlsx'
)
print(df.duplicated())
0 False
1 False
2 False
3 True
4 False
5 False
6 False
7 False
8 True
9 True
duplicated方法
返回的是数据集每一行的检验结果,即10行数据会返回10个bool值。
为了能够得到最直接的结果,可以使用any函数
。该函数表示的是在多个条件判断中,只要有一个条件为True,则any函数的结果就为True。
print(any(df.duplicated()))
删除重复项:
df.drop_duplicates(inplace=True)
结果:
appcategory appname comments install love size update
0 网上购物-商城-团购-优惠-快递 每日优鲜 1297 204.7万 89.00% 15.16MB 2017年10月11日
1 网上购物-商城 苏宁易购 577 7996.8万 73.00% 58.9MB 2017年09月21日
2 网上购物-商城-优惠 唯品会 2543 7090.1万 86.00% 41.43MB 2017年10月13日
4 网上购物-商城 拼多多 1921 3841.9万 95.00% 13.35MB 2017年10月11日
5 网上购物-商城-优惠 寺库奢侈品 1964 175.4万 100.00% 17.21MB 2017年09月30日
6 网上购物-商城 淘宝 14244 4.6亿 68.00% 73.78MB 2017年10月13日
7 网上购物-商城-团购-优惠 当当 134 1615.3万 61.00% 37.01MB 2017年10月17日
完整代码:
df = pd.read_excel(
io='./data_test04.xlsx'
)
print(any(df.duplicated()))
df.drop_duplicates(inplace=True)
print(df)
缺失值处理
缺失值是指数据集中的某些观测存在遗漏的指标值,缺失值的存在同样会影响到数据分析和挖掘的结果。
一般而言,当遇到缺失值(Python中用NaN表示)时,可以采用三种方法处置,分别是删除法
、替换法
和插补法
。
删除法
是指当缺失的观测比例非常低时(如5%以内),直接删除存在缺失的观测,或者当某些变量的缺失比例非常高时(如85%以上),直接删除这些缺失的变量;
替换法
是指用某种常数直接替换那些缺失值,例如,对连续变量而言,可以使用均值或中位数替换,对于离散变量,可以使用众数替换;
插补法
是指根据其他非缺失的变量或观测来预测缺失值,常见的插补法有回归插补法、K近邻插补法、拉格朗日插补法等。
待处理数据集:
判断数据集是否存在缺失值:
print(df.isnull())
uid regit_date gender age income
0 False False False False False
1 False False False False False
2 False False False True False
3 False False False False False
4 False False True True False
5 False False False False True
6 False False False False False
7 False False False True False
8 False False False False False
9 False False False False True
-
删除法
- 删除记录(行)
所有含缺失值的行记录全部删除
df.dropna(inplace=True)
uid regit_date gender age income 0 81200457 2016-10-30 M 23.0 6500.0 1 81201135 2016-11-08 M 27.0 10300.0 3 84639281 2017-04-17 M 26.0 6000.0 6 63881943 2015-10-07 M 21.0 10000.0 8 77638351 2016-07-12 M 25.0 18000.0
- 删除变量(列)
df.drop('age',axis=1,inplace=True)
uid regit_date gender income 0 81200457 2016-10-30 M 6500.0 1 81201135 2016-11-08 M 10300.0 2 80043782 2016-10-13 F 13500.0 3 84639281 2017-04-17 M 6000.0 4 73499801 2016-03-21 NaN 4500.0 5 72399510 2016-01-18 M NaN 6 63881943 2015-10-07 M 10000.0 7 35442690 2015-04-10 F 5800.0 8 77638351 2016-07-12 M 18000.0 9 85200189 2017-05-18 M NaN
-
替换法
缺失值的替换需要借助于
fillna方法
,该方法中的method参数可以接受'ffill'
和'bfill'
两种值,分别代表前向填充和后向填充。缺失值的前向填充或后向填充一般适用于
时间序列型
的数据集,因为这样的数据前后具有连贯性,而一般的独立性样本并不适用该方法。- 前向替换
前向填充是指用缺失值的前一个值替换
df.fillna(method='ffill',inplace=True)
- 后向替换
后向填充则表示用缺失值的后一个值替换
df.fillna(method='bfill',inplace=True)
- 常数替换
有些情况是有用的,例如某人确实没有工作,故收入为0,但是该方法就是典型的“以点概面”,非常容易导致错误,例如结果中的性别莫名多出异样的0值。
df.fillna(value=0,inplace=True)
- 统计值替换
采用了更加灵活的替换方法,即**
分别对各缺失变量使用不同的替换值
**(需要采用字典的方式传递给value参数),性别使用众数
替换,年龄使用均值
替换,收入使用中位数
替换。df.fillna(value={ 'gender':df.gender.mode()[0], 'age':df.age.mean(), 'income':df.income.median() },inplace=True)
异常值处理
异常值是指那些远离正常值的观测,即“不合群”观测。
对于异常值的检测,一般采用两种方法,一种是n个标准差法,另一种是箱线图判别法。
标准差法判别公式: o u t l i n e a r > ∣ x ‾ ± n σ ∣ outlinear > |\overline{x} \pm n\sigma| outlinear>∣x±nσ∣,其中 x ‾ \overline{x} x为样本均值, σ \sigma σ为样本标准差。
当
n=2
时,满足条件的观测就是异常值
,当n=3
时,满足条件的观测就是极端异常值
;
箱线图判别公式: o u t l i n e a r > Q 3 + n I Q R outlinear > Q3 + nIQR outlinear>Q3+nIQR 或者 o u t l i n e a r < Q 1 − n I Q R outlinear < Q1 - nIQR outlinear<Q1−nIQR,其中Q1为下四分位数(25%),Q3为上四位数(75%),IQR为四分位差(上四分位数与下四分位数的差)。
当
n=1.5
时,满足条件的观测为异常值
,当n=3
时,满足条件的观测即为极端异常值
。
两种方法的选择标准:
如果数据近似服从正态分布时,优先选择n个标准差法
,因为数据的分布相对比较对称;否则优先选择箱线图法
,因为分位数并不会受到极端值的影响。
当数据存在异常时,一般可以使用删除法将异常值删除
(前提是异常观测的比例不能太大)、替换法
(可以考虑使用低于判别上限的最大值或高于判别下限的最小值替换、使用均值或中位数替换等)。
注意:counts是sunspots的一列,不是dataframe的属性。
sunspots = pd.read_csv(
filepath_or_buffer='./sunspots.csv'
)
# 标准差法
xbar = sunspots.counts.mean()
xstd = sunspots.counts.std()
print(f'标准差法异常值上限检测:{any(sunspots.counts > xbar + 2 * xstd)}')
print(f'标准差法异常值下限检测:{any(sunspots.counts < xbar - 2 * xstd)}')
# 箱线图法
Q1 = sunspots.counts.quantile(q=0.25)
Q3 = sunspots.counts.quantile(q=0.75)
IQR = Q3-Q1
print(f'箱线图法异常值上限检测:{any(sunspots.counts > Q3 + 1.5 % IQR)}')
print(f'箱线图法异常值上限检测:{any(sunspots.counts < Q1 - 1.5 % IQR)}')
标准差法异常值上限检测:True
标准差法异常值下限检测:False
箱线图法异常值上限检测:True
箱线图法异常值上限检测:True
如上结果所示,不管是标准差检验法还是箱线图检验法,都发现太阳黑子数据中存在异常值,而且异常值都是超过上限临界值的。接下来,通过绘制太阳黑子数量的直方图
和核密度曲线图
,用于检验数据是否近似服从正态分布,进而选择一个最终的异常值判别方法:
import matplotlib.pyplot as plt
# 设置绘图风格
plt.style.use('ggplot')
# 绘制直方图
sunspots.counts.plot(kind='hist', bins=30,density=True,stacked=True)
# 绘制核密度图
sunspots.counts.plot(kind='kde')
plt.show()
不管是直方图还是核密度曲线,所呈现的数据分布形状都是有偏的,并且属于右偏。基于此,这里选择箱线图法
来判定太阳黑子数据中的那些异常值。
这里就使用替换法
来处理异常值,即使用低于判别上限的最大值或高于判别下限的最小值替换,代码如下:
print(sunspots.counts.describe())
# 箱线图中的异常值判别上限
UL = Q3 + 1.5 * IQR
print(UL)
# 从数据中找出低于判别上限的最大值
replace_value = sunspots.counts[sunspots.counts < UL].copy().max()
print(replace_value)
# 查看异常值的个数
print(sunspots.counts[sunspots.counts > UL].describe().count)
# 替换超过判别上限的异常值
sunspots.counts[sunspots.counts > UL] = replace_value
print(sunspots.counts.describe())
数据子集的获取
在Pandas模块中实现数据框子集的获取可以使用iloc、loc和ix三种“方法”,它们的语法可以表示成[rows_select,cols_select]
。
- iloc只能通过
行号
和列号
进行数据的筛选,可以将iloc中的“i”理解为“integer”,,该索引方式与数组的索引方式类似,都是从0开始,可以间隔取号,对于切片仍然无法取到上限。 - loc要比iloc灵活一些,可以将loc中的“l”理解为“label”,即可以向[rows_select,cols_select]指定具体的行标签(行名称)和列标签(字段名),可以将rows_select指定为具体的筛选条件,在iloc中是无法做到的。
df1 = pd.DataFrame({
'name': ['张三', '李四', '网二', '丁一', '李武'],
'gender': ['男', '女', '女', '女', '男'],
'age': [23, 26, 22, 25, 27]
}, columns=['name', 'gender', 'age'])
print(df1)
# 取出所有女性并且返回姓名和年龄
print(df1.iloc[1:4,[0,2]])
print(df1.loc[1:3,['name','age']])
# 条件索引
index = df1.gender[df1.gender=='女'].index
print(df1.iloc[index,:])
name gender age
0 张三 男 23
1 李四 女 26
2 网二 女 22
3 丁一 女 25
4 李武 男 27
name gender age
1 李四 女 26
2 网二 女 22
3 丁一 女 25
假如数据集没有行号,而是具体的行名称。
df2 = df1.set_index('name')
print(df2)
# 取出数据集的中间三行
print(df2.iloc[1:4,:])
print(df2.loc[['李四','网二','丁一'],:])
gender age
name
张三 男 23
李四 女 26
网二 女 22
丁一 女 25
李武 男 27
gender age
name
李四 女 26
网二 女 22
丁一 女 25
对于iloc来说
,不管什么形式的数据集都可以使用,始终表示行索引
,即取哪些行下标的观测;
loc
就不能使用数值表示行标签了,因为此时数据集的行标签是姓名,所以需要写入中间三行对应的姓名;
布尔索引:
# 取出所有男性的姓名和年龄
# iloc不允许使用条件筛选
print(df1.iloc[df1.gender == '男', [0, 2]])
print(df1.iloc[df1.gender[df1.gender == '男'].index, [0, 2]])
print(df1.loc[df1.gender == '男', ['name', 'age']])
透视表功能
Pandas模块提供实现透视表功能的pivot_table函数
。
- 单个分组变量的均值统计
diamonds = pd.read_table(
filepath_or_buffer=r'./diamonds.csv',
sep=','
)
print(diamonds)
print(pd.pivot_table(data=diamonds, index='color', values='price', margins='总计', margins_name='总计'))
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75
... ... ... ... ... ... ... ... ... ... ...
53935 0.72 Ideal D SI1 60.8 57.0 2757 5.75 5.76 3.50
53936 0.72 Good D SI1 63.1 55.0 2757 5.69 5.75 3.61
53937 0.70 Very Good D SI1 62.8 60.0 2757 5.66 5.68 3.56
53938 0.86 Premium H SI2 61.0 58.0 2757 6.15 6.12 3.74
53939 0.75 Ideal D SI2 62.2 55.0 2757 5.83 5.87 3.64
[53940 rows x 10 columns]
price
color
D 3169.954096
E 3076.752475
F 3724.886397
G 3999.135671
H 4486.669196
I 5091.874954
J 5323.818020
总计 3932.799722
- 两个分组变量的列联表
print(pd.pivot_table(data=diamonds,
index='clarity',
columns='cut',
values='carat',
aggfunc=np.size,
margins=True,
margins_name='总计'
))
cut Fair Good Ideal Premium Very Good 总计
clarity
I1 210 96 146 205 84 741
IF 9 71 1212 230 268 1790
SI1 408 1560 4282 3575 3240 13065
SI2 466 1081 2598 2949 2100 9194
VS1 170 648 3589 1989 1775 8171
VS2 261 978 5071 3357 2591 12258
VVS1 17 186 2047 616 789 3655
VVS2 69 286 2606 870 1235 5066
总计 1610 4906 21551 13791 12082 53940
对于列联表来说
,行和列都需要指定某个分组变量,所以index参数
和columns参数
都需要指定一个分组变量,并且统计的不再是某个变量的均值,而是观测个数,所以aggfunc参数
需要指定numpy模块中的size函数。
表之间的合并与连接
Pandas模块同样提供了关于多表之间的合并和连接操作函数,分别是concat函数
和merge函数
,首先介绍一下这两个函数的用法和重要参数含义。
- 合并函数concat
针对合并函数concat,需要强调:
- 如果
纵向合并多个数据集
,即使这些数据集都含有“姓名”变量,但变量名称不一致,如Name和name,通过合并后,将会得到错误的结果。
-
连接函数merge
该函数的最大
缺点
是,每次只能操作两张数据表的连接,如果有n张表需要连接,则必须经过n-1次的merge函数使用。# 构造数据集 df3 = pd.DataFrame({ 'id': [1, 2, 3, 4, 5], 'name': ['张三', '李四', '王二', '丁一', '赵五'], 'age': [27, 24, 25, 23, 25], 'gender': ['男', '男', '男', '女', '女'] }) df4 = pd.DataFrame({ 'Id': [1, 2, 2, 4, 4, 4, 5], 'kemu': ['科目1', '科目1', '科目2', '科目1', '科目2', '科目3', '科目1'], 'score': [83, 81, 87, 75, 86, 74, 88] }) df5 = pd.DataFrame({ 'id': [1, 3, 5], 'name': ['张三', '王二', '赵五'], 'income': [13500, 18000, 15000] }) # 三表的数据连接 # 先连接df3和df4 merge1 = pd.merge(left=df3,right=df4,left_on='id',right_on='Id',how='left') print(merge1) print('*'*50) # 再连接df5 merge2 = pd.merge(left=merge1,right=df5,left_on='id',right_on='id',how='left') print(merge2)
id name age gender Id kemu score 0 1 张三 27 男 1.0 科目1 83.0 1 2 李四 24 男 2.0 科目1 81.0 2 2 李四 24 男 2.0 科目2 87.0 3 3 王二 25 男 NaN NaN NaN 4 4 丁一 23 女 4.0 科目1 75.0 5 4 丁一 23 女 4.0 科目2 86.0 6 4 丁一 23 女 4.0 科目3 74.0 7 5 赵五 25 女 5.0 科目1 88.0 ************************************************** id name_x age gender Id kemu score name_y income 0 1 张三 27 男 1.0 科目1 83.0 张三 13500.0 1 2 李四 24 男 2.0 科目1 81.0 NaN NaN 2 2 李四 24 男 2.0 科目2 87.0 NaN NaN 3 3 王二 25 男 NaN NaN NaN 王二 18000.0 4 4 丁一 23 女 4.0 科目1 75.0 NaN NaN 5 4 丁一 23 女 4.0 科目2 86.0 NaN NaN 6 4 丁一 23 女 4.0 科目3 74.0 NaN NaN 7 5 赵五 25 女 5.0 科目1 88.0 赵五 15000.0
分组聚合
以珠宝数据为例,统计各颜色和刀工组合下
的珠宝数量、最小重量、平均价格和最大面宽。如果读者对SQL比较熟悉的话,可以写成下方的SQL代码,实现数据的统计:
diamonds = pd.read_csv(filepath_or_buffer='./diamonds.csv', sep=',')
# print(diamonds)
# 通过groupby方法,指定分组变量
grouped = diamonds.groupby(by=['color', 'cut'])
# 对分组变量进行统计汇总
res = grouped.aggregate({
'color': np.size,
'carat': np.min,
'price': np.mean
# 'face_width': np.max
})
print(res)
print('*'*50)
# 数据集重命名
res.rename(columns={
'color': 'counts',
'carat': 'min_weight',
'price': 'avg_price'
}, inplace=True)
print(res)
print('*'*50)
# 将行索引转换为数据框的变量
res.reset_index(inplace=True)
print(res)
color carat price
color cut
D Fair 163 0.25 4291.061350
Good 662 0.23 3405.382175
Ideal 2834 0.20 2629.094566
Premium 1603 0.20 3631.292576
Very Good 1513 0.23 3470.467284
E Fair 224 0.22 3682.312500
Good 933 0.23 3423.644159
Ideal 3903 0.20 2597.550090
Premium 2337 0.20 3538.914420
Very Good 2400 0.20 3214.652083
F Fair 312 0.25 3827.003205
Good 909 0.23 3495.750275
Ideal 3826 0.23 3374.939362
Premium 2331 0.20 4324.890176
Very Good 2164 0.23 3778.820240
G Fair 314 0.23 4239.254777
Good 871 0.23 4123.482204
Ideal 4884 0.23 3720.706388
Premium 2924 0.23 4500.742134
Very Good 2299 0.23 3872.753806
H Fair 303 0.33 5135.683168
Good 702 0.25 4276.254986
Ideal 3115 0.23 3889.334831
Premium 2360 0.23 5216.706780
Very Good 1824 0.23 4535.390351
I Fair 175 0.41 4685.445714
Good 522 0.30 5078.532567
Ideal 2093 0.23 4451.970377
Premium 1428 0.23 5946.180672
Very Good 1204 0.24 5255.879568
J Fair 119 0.30 4975.655462
Good 307 0.28 4574.172638
Ideal 896 0.23 4918.186384
Premium 808 0.30 6294.591584
Very Good 678 0.24 5103.513274
**************************************************
counts min_weight avg_price
color cut
D Fair 163 0.25 4291.061350
Good 662 0.23 3405.382175
Ideal 2834 0.20 2629.094566
Premium 1603 0.20 3631.292576
Very Good 1513 0.23 3470.467284
E Fair 224 0.22 3682.312500
Good 933 0.23 3423.644159
Ideal 3903 0.20 2597.550090
Premium 2337 0.20 3538.914420
Very Good 2400 0.20 3214.652083
F Fair 312 0.25 3827.003205
Good 909 0.23 3495.750275
Ideal 3826 0.23 3374.939362
Premium 2331 0.20 4324.890176
Very Good 2164 0.23 3778.820240
G Fair 314 0.23 4239.254777
Good 871 0.23 4123.482204
Ideal 4884 0.23 3720.706388
Premium 2924 0.23 4500.742134
Very Good 2299 0.23 3872.753806
H Fair 303 0.33 5135.683168
Good 702 0.25 4276.254986
Ideal 3115 0.23 3889.334831
Premium 2360 0.23 5216.706780
Very Good 1824 0.23 4535.390351
I Fair 175 0.41 4685.445714
Good 522 0.30 5078.532567
Ideal 2093 0.23 4451.970377
Premium 1428 0.23 5946.180672
Very Good 1204 0.24 5255.879568
J Fair 119 0.30 4975.655462
Good 307 0.28 4574.172638
Ideal 896 0.23 4918.186384
Premium 808 0.30 6294.591584
Very Good 678 0.24 5103.513274
**************************************************
color cut counts min_weight avg_price
0 D Fair 163 0.25 4291.061350
1 D Good 662 0.23 3405.382175
2 D Ideal 2834 0.20 2629.094566
3 D Premium 1603 0.20 3631.292576
4 D Very Good 1513 0.23 3470.467284
5 E Fair 224 0.22 3682.312500
6 E Good 933 0.23 3423.644159
7 E Ideal 3903 0.20 2597.550090
8 E Premium 2337 0.20 3538.914420
9 E Very Good 2400 0.20 3214.652083
10 F Fair 312 0.25 3827.003205
11 F Good 909 0.23 3495.750275
12 F Ideal 3826 0.23 3374.939362
13 F Premium 2331 0.20 4324.890176
14 F Very Good 2164 0.23 3778.820240
15 G Fair 314 0.23 4239.254777
16 G Good 871 0.23 4123.482204
17 G Ideal 4884 0.23 3720.706388
18 G Premium 2924 0.23 4500.742134
19 G Very Good 2299 0.23 3872.753806
20 H Fair 303 0.33 5135.683168
21 H Good 702 0.25 4276.254986
22 H Ideal 3115 0.23 3889.334831
23 H Premium 2360 0.23 5216.706780
24 H Very Good 1824 0.23 4535.390351
25 I Fair 175 0.41 4685.445714
26 I Good 522 0.30 5078.532567
27 I Ideal 2093 0.23 4451.970377
28 I Premium 1428 0.23 5946.180672
29 I Very Good 1204 0.24 5255.879568
30 J Fair 119 0.30 4975.655462
31 J Good 307 0.28 4574.172638
32 J Ideal 896 0.23 4918.186384
33 J Premium 808 0.30 6294.591584
34 J Very Good 678 0.24 5103.513274