# 一、评估数据
评估数据时,我们主要看两方面:结构和内容
# (一)、结构
- 结构方面需要清理的数据叫做乱数据;结构方面不需要清理的数据叫做整洁数据。
- 整洁数据,根据埃德加科德的第三范式,包括以下三个特点:
1)每列是一个变量
2)每行是一个观察值
3)每个单元格是一个值
任何不符合以上三个特点的数据都是乱数据。
不整洁的数据存在多种多样的例子:比如存在合并单元格的表格或者每列存在多个变量。
很多方便人类理解的数据,并不是整洁数据;
而整洁数据有时不太直观。
因为整洁数据并不是方便人类理解,而是让代码更加高效处理和达成分析目的的。
# (二)、内容
# 1、概念
内容方面需要清理的数据叫脏数据,内容方面不需要清理的数据叫干净数据。
# 2、脏数据可能存在问题
- 丢失数据
- 有些值为空缺。
- 空缺值的影响大小取决于具体情况,有些时候允许某些列的数据不全。
- 有些值为空缺。
- 但我们仍然要进行评估,否则可能导致错误的分析,比如:如果我们没有考虑到有同学缺考,存在成绩缺失,直接用总分数除以总人头数求平均,就会导致计算结果被缺失值拉低。
- 重复数据
- 即数据中有些观察值重复出现。
- 有些值的重复不是问题,比如说班级里学生的性别。
- 但假如数据中作为唯一表示符的属性存在重复,或者数据中存在有数据实例所有属性值均相同的情况,就是有问题
- 不一致数据
- 即不同数据值实际含义相同,或不同数据值实际指代统一目标
- 比如:数字单位不一致、全称和简写混用、中文数字和阿拉伯数字混用等等
- 无效或错误数据
- 脱离现实规则的数据都是无效数据,比如:负数的身高
- 虽然符合规则但并不准确的数据是错误数据,比如:一个成年人的体重是3公斤
# 二、评估数据整洁度
数据的整洁度比干净度更好评估,因为结构性问题相比内容性问题,更直观和容易被发现。
通过DataFrame的概况信息,和实际数据,来评估数据的整洁度。
# (一)、info方法列名
DataFrame的info方法,它能提供数据的概况信息,其中列名首先能透露一些关于结构的信息。
比如:如果有列叫做'销售额_2010',说明这列应该是同时包含了销售额和年份两个变量,不符合每列只能表示一个变量的整洁性规则。
# (二)、head/tail/sample方法
- 通过head/tail/sample方法,来获取开头/结尾/随机N行。
通过部分数据来评估,是否符合每列是一个变量,每行是一个观察值,每个单元格是一个值的规则。
- 在输出部分行检查结构时,如果DataFrame由于列数太多,或者值太长,而展示不全的话,可以通过set_option函数,调整列数或值长度展示的上限。
# 三、评估数据的干净度
准备数据
import pandas as pd
import numpy as np
scores = pd.DataFrame({"001": {"姓名": "小陈", "考试1": 85, "考试2": 95, "考试3": 92}, "002": {"姓名": "小李", "考试1": 91, "考试2": 92, "考试3": 94}, "003": {"姓名": "小王", "考试1": 86, "考试2": 81, "考试3": np.nan}, "004": {"姓名": "小张", "考试1": 79, "考试2": 89, "考试3": 95}, "005": {"姓名": "小赵", "考试1": 96, "考试2": 91, "考试3": 91}, "006": {"姓名": "小周", "考试1": 81, "考试2": np.nan, "考试3": 92} }).T
scores
2
3
4
姓名 | 考试1 | 考试2 | 考试3 | |
---|---|---|---|---|
001 | 小陈 | 85 | 95 | 92 |
002 | 小李 | 91 | 92 | 94 |
003 | 小王 | 86 | 81 | NaN |
004 | 小张 | 79 | 89 | 95 |
005 | 小赵 | 96 | 91 | 91 |
006 | 小周 | 81 | NaN | 92 |
# (一)、评估丢失数据
# 1、info方法
将行的数量,与每列非空缺值的数量进行对比,就能知道这列是否存在空缺值。
# 2、isnull方法
Series和DataFrame都有一个方法叫isnull,作用是检查值是否为空缺值
# 1)Series中的isnull方法
用在Series上,它会返回一个布尔值组成的Series,告诉我们原Series里的各个值是否为空缺值,是的话就是True,不是就是False
scores.考试2
001 95
002 92
003 81
004 89
005 91
006 NaN
Name: 考试2, dtype: object
2
3
4
5
6
7
8
9
scores.考试2.isnull()
001 False
002 False
003 False
004 False
005 False
006 True
Name: 考试2, dtype: bool
2
3
4
5
6
7
8
9
# 2)DataFrame中的isnull方法
用在DataFrame上,会返回一个布尔值组成的DataFrame,告诉我们原DataFrame里各个值是否为空缺值。
scores
姓名 | 考试1 | 考试2 | 考试3 | |
---|---|---|---|---|
001 | 小陈 | 85 | 95 | 92 |
002 | 小李 | 91 | 92 | 94 |
003 | 小王 | 86 | 81 | NaN |
004 | 小张 | 79 | 89 | 95 |
005 | 小赵 | 96 | 91 | 91 |
006 | 小周 | 81 | NaN | 92 |
scores.isnull()
姓名 | 考试1 | 考试2 | 考试3 | |
---|---|---|---|---|
001 | False | False | False | False |
002 | False | False | False | False |
003 | False | False | False | True |
004 | False | False | False | False |
005 | False | False | False | False |
006 | False | False | True | False |
直接对DataFrame调用isnull方法,并不能直观的评估丢失数据
# 3)提取丢失数据的行
根据条件筛选DataFrame中的行的原理是:DataFrame的列是Series类型,而Series和条件结合起来,会返回一个布尔值组成的Series,它的长度和DataFrame的行数相对应。DataFrame会用布尔值的Series进行索引,保留True所对应的索引的行。
而在这里,对DataFrame的列调用isnull方法,也会返回一个布尔值组成的Series。将其放在方括号里,DataFrame会用布尔值的Series进行索引,保留True所对应的索引的行,即提取出那列丢失数据的行。
scores[scores["考试3"].isnull()]
姓名 | 考试1 | 考试2 | 考试3 | |
---|---|---|---|---|
003 | 小王 | 86 | 81 | NaN |
# 3、连续调用isnull方法和sum方法
由于True和False布尔值,用在数学计算里,会被分别视为数字1和数字0,所以再调用求和方法,就是在算True的数量
True + 5
6
False + 5
5
2
3
4
# 1)Series
计算空缺值的数量
scores['考试2'].isnull().sum()
1
2
# 2)DataFrame
由于sum方法默认沿着纵向操作,会返回一个列名作为标签索引的Series;
又由于DataFrame的列就是Series,所以返回的Series的值,就是各个列里空缺值的数量
a、计算某列空缺值的个数
scores.考试3.isnull().sum()
1
2
b、计算每列空缺值的个数
scores.isnull().sum()
姓名 0
考试1 0
考试2 1
考试3 1
dtype: int64
2
3
4
5
6
7
# (二)、评估重复数据
Series和DataFrame都有一个方法叫duplicated,作用是检查值或行是否存在重复
name = pd.Series(["小陈", "小李", "小王", "小张", "小赵", "小李", "小周"])
gender = pd.Series(["女", "女", "男", "男", "女", "女", "女"])
height = pd.Series([172.5, 168.0, 178.2, 181.3, 161.7, 168.0, 158.5])
student_number = pd.Series(["001", "002", "003", "002", "005", "002", "006"])
students = pd.DataFrame({"学号": student_number, "姓名": name, "性别": gender, "身高": height})
students
2
3
4
5
6
学号 | 姓名 | 性别 | 身高 | |
---|---|---|---|---|
0 | 001 | 小陈 | 女 | 172.5 |
1 | 002 | 小李 | 女 | 168.0 |
2 | 003 | 小王 | 男 | 178.2 |
3 | 002 | 小张 | 男 | 181.3 |
4 | 005 | 小赵 | 女 | 161.7 |
5 | 002 | 小李 | 女 | 168.0 |
6 | 006 | 小周 | 女 | 158.5 |
# 1、Series中的duplicated方法
会返回布尔值组成的Series,表明当前值是否在前面存在过,某个值第一次出现时是False,说明前面不存在重发;但当它第二次、第三次出现时是True,说明这个属于重复值
students.学号
0 001
1 002
2 003
3 002
4 005
5 002
6 006
Name: 学号, dtype: object
2
3
4
5
6
7
8
9
10
students.学号.duplicated()
0 False
1 False
2 False
3 True
4 False
5 True
6 False
Name: 学号, dtype: bool
2
3
4
5
6
7
8
9
10
# 2、DataFrame中的duplicated方法
会返回布尔值组成的Series
# 1)查看所有属性是否存在重复
默认查看所有属性是否存在重复,当这一行里所有值和前面某行完全一样时,才会是True
students.duplicated()
0 False
1 False
2 False
3 False
4 False
5 True
6 False
dtype: bool
2
3
4
5
6
7
8
9
10
# 2)查看任意属性是否存在重复
给duplicated传入可选参数subset,赋值给一个列表,里面放上我们想查看是否存在重复的任意属性。当这些属性同时出现重复的时候,才会是True
students.duplicated(subset=['学号', '性别'])
0 False
1 False
2 False
3 False
4 False
5 True
6 False
dtype: bool
2
3
4
5
6
7
8
9
10
# 3)提取重复数据的行
原理是根据条件筛选DataFrame中的行。
在这里,对DataFrame调用duplicated方法时,会返回一个布尔值组成的Series。将其放在方括号里,DataFrame会用布尔值的Series进行索引,保留True所对应的索引的行,即提取出包含重复数据的行。
对于长数据,评估重复数据,无法直接查看,只能提取重复数据的行
students[students.duplicated(subset=['学号', '性别'])]
学号 | 姓名 | 性别 | 身高 | |
---|---|---|---|---|
5 | 002 | 小李 | 女 | 168.0 |
# (三)、评估不一致数据
Series的value_counts方法
会返回Series里各个值的个数。
我们可以用value_counts,看到DataFrame某列下面所有值的种类,以及各个种类出现的次数,帮我们确定有没有不同值在表示同一目标的情况。
name = pd.Series(["小陈", "小李", "小李", "小王", "小张", "小赵", "小周"])
gender = pd.Series(["女", "女", "女", "男", "男", "女", "女"])
class_number = pd.Series(["1 班", "1班", "1 班", "一班", "二班", "2 班", "2 班"])
student_number = pd.Series(["001", "002", "002", "003", "004", "005", "006"])
students2 = pd.DataFrame({"学号": student_number, "姓名": name, "性别": gender, "班级": class_number})
students2
2
3
4
5
6
学号 | 姓名 | 性别 | 班级 | |
---|---|---|---|---|
0 | 001 | 小陈 | 女 | 1 班 |
1 | 002 | 小李 | 女 | 1班 |
2 | 002 | 小李 | 女 | 1 班 |
3 | 003 | 小王 | 男 | 一班 |
4 | 004 | 小张 | 男 | 二班 |
5 | 005 | 小赵 | 女 | 2 班 |
6 | 006 | 小周 | 女 | 2 班 |
students2['班级']
0 1 班
1 1班
2 1 班
3 一班
4 二班
5 2 班
6 2 班
Name: 班级, dtype: object
2
3
4
5
6
7
8
9
10
students2['班级'].value_counts()
1 班 2
2 班 2
1班 1
一班 1
二班 1
Name: 班级, dtype: int64
2
3
4
5
6
7
8
# (四)、评估无效/错误数据
评估无效/错误数据的评估难度很大,比如拿到一个陌生数据集的时候,无法知道上面的地址是不是有效的,某个人的身高是不是准确的,这些都超出了我们能够评估的范围。
但对于特别离谱的数据,还是可以通过某些方法,结合常识,来检查是否存在无效数据
name = pd.Series(["小陈", "小李", "小李", "小王", "小张", "小赵", "小周"])
gender = pd.Series(["女", "女", "女", "男", "男", "女", "女"])
height = pd.Series([172.5, 168.0, 1168.0, 178.2, 181.3, -161.7, 158.5])
students3 = pd.DataFrame({"姓名": name, "性别": gender, "身高": height})
students3
2
3
4
5
姓名 | 性别 | 身高 | |
---|---|---|---|
0 | 小陈 | 女 | 172.5 |
1 | 小李 | 女 | 168.0 |
2 | 小李 | 女 | 1168.0 |
3 | 小王 | 男 | 178.2 |
4 | 小张 | 男 | 181.3 |
5 | 小赵 | 女 | -161.7 |
6 | 小周 | 女 | 158.5 |
# 1、Series的sort_values方法
可以对某列的值进行排序,能帮我们发现异常的小值或异常的大值
students3.身高.sort_values()
5 -161.7
6 158.5
1 168.0
0 172.5
3 178.2
4 181.3
2 1168.0
Name: 身高, dtype: float64
2
3
4
5
6
7
8
# 2、describe方法
会展示数字列的最小值、最大值等,可以帮我们确实是否有异常数据的存在
students3.describe()
身高 | |
---|---|
count | 7.000000 |
mean | 266.400000 |
std | 416.596447 |
min | -161.700000 |
25% | 163.250000 |
50% | 172.500000 |
75% | 179.750000 |
max | 1168.000000 |
除了这些之外,数据还可能存在其它类型的问题,或者需要其它方法才能评估。