文章目录
1 数据质量的准则
-
- 完整性:单条数据是否存在空值,统计的字段是否完善。
-
- 全面性:观察某一列的全部数值,比如在Excel表中,我们选中一列,可以看到该列的平均值、最大值、最小值。我们可以通过常识来判断该列是否有问题,比如:数据定义、单位标识、数值本身。
-
- 合法性:数据的类型、内容、大小的合法性。比如数据中存在非ASCII字符,性别存在了未知,年龄超过了150岁等。
-
- 唯一性:数据是否存在重复记录,因为数据通常来自不同渠道的汇总,重复的情况是常见的。行数据、列数据都需要是唯一的,比如一个人不能重复记录多次,且一个人的体重也不能在列指标中重复记录多次。
2 处理缺失数据
在数据表或 DataFrame 中有很多识别缺失值的方法。一般情况下可以分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个标签值(sentinel value)表示缺失值。在掩码方法中,掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(0 或 1)表示有缺失值的局部状态。
在标签方法中,标签值可能是具体的数据(例如用 -9999 表示缺失的整数),也可能是些极少出现的形式。另外,标签值还可能是更全局的值,比如用 NaN(不是一个数)表示缺失的浮点数
2.1缺失值
- None:Python对象类型的缺失值
Pandas 可以使用的第一种缺失值标签是 None,它是一个 Python 单体对象,经常在代码中表示缺失值。由于 None 是一个 Python 对象,所以不能作为任何 NumPy / Pandas 数组类型的缺失值,只能用于 ‘object’ 数组类型(即由 Python 对象构成的数组):
import pandas as pd
import numpy as np
np.array([1, None, 3, 4])
输出:
array([1, None, 3, 4], dtype=object)
这里 dtype=object 表示 NumPy 认为由于这个数组是 Python 对象构成的,因此将其类型判断为 object。虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在 Python 层面完成,但是在进行常见的快速操作时,这种类型比其他原生类型数组要消耗更多的资源
使用 Python 对象构成的数组就意味着如果你对一个包含 None 的数组进行累计操作,如 sum() 或者 min(),那么通常会出现类型错误:
print(np_arr.sum()) # 报错
Pandas中NaN与None的差异
# NaN与 None的差异
ser = pd.Series([1, np.nan, 2, None])
print(ser)
"""
0 1.0
1 NaN
2 2.0
3 NaN
dtype: float64
"""
Pandas 会将没有标签值的数据类型自动转换为 NA。例如,当我们将整型数组中的一个值设置为 np.nan 时,这个值就会强制转换成浮点数缺失值 NA。
ser2 = pd.Series(range(2), dtype=int)
ser2[0] = None
print(ser2)
"""
0 NaN
1 1.0
dtype: float64
"""
Pandas对不同类型缺失值的转换规则
类型 | 缺失值转换规则 | NA标签值 |
---|---|---|
floating 浮点型 | 无变化 | np.nan |
object 对象类型 | 无变化 | None 或 np.nan |
integer 整数类型 | 强制转换为 float64 | np.nan |
boolean 布尔类型 | 强制转换为 object | None 或 np.nan |
2.2 处理缺失数据
Pandas 基本上把 None 和 NaN 看成是可以等价交换的缺失值形式。为了完成这种交换过程,Pandas 提供了一些方法来发现、剔除、替换数据结构中的缺失值,主要包括以下几种。
方法 | 说明 |
---|---|
isnull() | 创建一个布尔类型的掩码标签缺失值。 |
notnull() | 与 isnull() 操作相反。 |
dropna() | 返回一个剔除缺失值的数据。 |
fillna() | 返回一个填充了缺失值的数据副本。 |
判断空值
Pandas 数据结构有两种有效的方法可以发现缺失值:isnull() 和 notnull()。每种方法都返回布尔类型的掩码数据,例如:
ser = pd.Series([1, np.nan, 'hello', None])
print('空值索引:\n', ser.isnull())
print('非空值索引:\n', ser.notnull())
print('空值:\n', ser[ser.isnull()])
在 Series 里使用的 isnull() 和 notnull() 同样适用于DataFrame,产生的结果同样是布尔类型。
剔除缺失值
还有两种很好用的缺失值处理方法,分别是 dropna()(剔除缺失值)和 fillna()(填充缺失值)
# 去除缺失值
print('删除空值:\n', ser.dropna())
而在 DataFrame 上使用它们时需要设置一些参数,例如下面的 DataFrame:
import pandas as pd
import numpy as np
df = pd.DataFrame(data=[[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
输出:
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
"""删除缺失值"""
print('默认删除空行:\n', df.dropna())
默认删除空行:
0 1 2
1 2.0 3.0 5
# 删除空列
print('默认删除空行:\n', df.dropna())
print('删除空列:\n', df.dropna(axis=1))
默认删除空行:
0 1 2
1 2.0 3.0 5
删除空列:
2
0 2
1 5
2 6
但是这么做也会把非缺失值一并剔除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置 how 或 thresh 参数来满足,它们可以设置剔除行或列缺失值的数量阈值。
默认设置是 how=‘any’,也就是说只要有缺失值就剔除整行或整列(通过 axis 设置坐标轴)。你还可以设置 how=‘all’,这样就只会剔除全部是缺失值的行或列了:
print('行列均为空的时候才删除:\n', df.dropna(axis=1, how='all'))
还可以通过 thresh 参数设置保留至少多少个非 NA 值的行,从而实现更加个性化的配置:
print('保留至少2个非 NA 值的行:\n', df.dropna(thresh=2))
并且可以指定列进行剔除
print('指定列删除空值:\n', df.dropna(subset=[1, 2]))
print('指定列删除空值:\n', df.dropna(subset=[1]))
填充缺失值
import pandas as pd
import numpy as np
"""填充缺失值"""
df = pd.DataFrame([[np.nan, 2, np.nan, 0],
[3, 4, np.nan, 1],
[np.nan, np.nan, np.nan, 5],
[np.nan, 3, np.nan, 4]],
columns=list("ABCD"))
df
输出:
A B C D
0 NaN 2.0 NaN 0
1 3.0 4.0 NaN 1
2 NaN NaN NaN 5
3 NaN 3.0 NaN 4
我们将用一个单独的值来填充缺失值,例如用 0,也可以用缺失值前面的有效值来从前往后填充(forward-fill)与从后往前填充(back-fill)
print('使用0填充缺失值:\n', df.fillna(0))
print('从前往后填充:\n', df.fillna(method="ffill"))
print('后往前填充:\n', df.fillna(method="bfill"))
输出
使用0填充缺失值:
A B C D
0 0.0 2.0 0.0 0
1 3.0 4.0 0.0 1
2 0.0 0.0 0.0 5
3 0.0 3.0 0.0 4
从前往后填充:
A B C D
0 NaN 2.0 NaN 0
1 3.0 4.0 NaN 1
2 3.0 4.0 NaN 5
3 3.0 3.0 NaN 4
后往前填充:
A B C D
0 3.0 2.0 NaN 0
1 3.0 4.0 NaN 1
2 NaN 3.0 NaN 5
3 NaN 3.0 NaN 4
"""可以修改填充轴"""
print('列填充:\n', df.fillna(method='ffill', axis=1))
输出:
列填充:
A B C D
0 NaN 2.0 2.0 0.0
1 3.0 4.0 4.0 1.0
2 NaN NaN NaN 5.0
3 NaN 3.0 3.0 4.0
并且可以自定义指定列进行填充
values = {"A": 0, "B": 1, "C": 2, "D": 3}
print('指定列进行填充:\n', df.fillna(value=values))
print('只填充一个:\n', df.fillna(value=values, limit=1))
输出:
指定列进行填充:
A B C D
0 0.0 2.0 2.0 0
1 3.0 4.0 2.0 1
2 0.0 1.0 2.0 5
3 0.0 3.0 2.0 4
只填充一个:
A B C D
0 0.0 2.0 2.0 0
1 3.0 4.0 NaN 1
2 NaN 1.0 NaN 5
3 NaN 3.0 NaN 4
- 案例
df=pd.read_excel('./data/水电费.xlsx')
df
"""删除 门店 这一列有缺失值的行"""
df1 = df.dropna(subset='门店')
df1
"""将 6月 这一列的缺失值用平均值填充"""
six_month_avg = df['6月'].mean()
df1['6月'] = df1['6月'].fillna(six_month_avg)
df1
输出:
3 重复数据
方法 | 说明 |
---|---|
duplicated() | 返回布尔型Series表示每行是否为重复行 |
drop_duplicates() | 删除重复数据 |
3.1 处理重复数据
duplicated() 判断重读数据
- keep 参数
- first 保留重复数据的第一个
- last 保留重复数据最后一个
- False 将所有重复数据全部找出来
- subset: 指定列查找重复数据
import numpy as np
import pandas as pd
"""重复行"""
df = pd.DataFrame({
'brand': ['Yum Yum', 'Yum Yum', 'Indomie', 'Indomie', 'Indomie', 'Indomie'],
'style': ['cup', 'cup', 'cup', 'pack', 'pack', 'pack'],
'rating': [4, 4, 3.5, 15, 5, 5]
})
df
输出:
brand style rating
0 Yum Yum cup 4.0
1 Yum Yum cup 4.0
2 Indomie cup 3.5
3 Indomie pack 15.0
4 Indomie pack 5.0
5 Indomie pack 5.0
# 判断重复行,默认保留第一个
print(df.duplicated())
# 判断重复行,保留最后一个
print('保留最后一个:\n', df.duplicated(keep='last'))
输出:
0 False
1 True
2 False
3 False
4 False
5 True
dtype: bool
保留最后一个:
0 True
1 False
2 False
3 False
4 True
5 False
dtype: bool
# 所有的重复行全部标注出来
print('全部标注:\n', df.duplicated(keep=False))
# 获取指定列的重复行
print('指定列:\n', df.duplicated(subset=['brand']))
全部标注:
0 True
1 True
2 False
3 False
4 True
5 True
dtype: bool
指定列:
0 False
1 True
2 False
3 True
4 True
5 True
dtype: bool
3.2 过滤重复行
drop_duplicates() 删除重复数据的方法
参数和 duplicated() 参数一模一样
- drop_duplicates()
- 默认判断全部列
- 可指定按某些列判断
df.drop_duplicates()
输出:
brand style rating
0 Yum Yum cup 4.0
2 Indomie cup 3.5
3 Indomie pack 15.0
4 Indomie pack 5.0
- 案例
df = pd.read_csv('./data/guazi.csv')
df
df.duplicated()
df.drop_duplicates()
4 数据替换
4.1 替换值
df = pd.DataFrame({'A': [0, 1, 2, 3, 4],
'B': [5, 6, 7, 8, 9],
'C': ['a', 'b', 'c', 'd', 'e']})
df
输出:
A B C
0 0 5 a
1 1 6 b
2 2 7 c
3 3 8 d
4 4 9 e
# 可以指定一个值进行替换
df.replace(0, 100)
输出:
A B C
0 100 5 a
1 1 6 b
2 2 7 c
3 3 8 d
4 4 9 e
4.1.1列表转换
df.replace([0, 5, 'b'], 200) # 将列表中的值替换成 200 多对一
df.replace([0, 5, 'b'], [100, 200, 300]) # 多对多, 需要一一对应
A B C
0 100 200 a
1 1 6 300
2 2 7 c
3 3 8 d
4 4 9 e
字典转换
"""字典替换"""
# df.replace({0: 10, 1:20, 2:30}) # 指定值进行替换, 根据键替换值
df.replace({'A': 0, 'B':5}, 100) # 可以按照列指定数据, 进行替换
df.replace({'A': {0: 10, 1:20, 2:30}}) # 只替换A这一列
输出
A B C
0 10 5 a
1 20 6 b
2 30 7 c
3 3 8 d
4 4 9 e
4.1.2正则替换
df = pd.DataFrame({'A': ['bat', 'foo', 'bait'],
'B': ['abc', 'bar', 'xyz']})
df
输出:
A B
0 bat abc
1 foo bar
2 bait xyz
# 正则表达式匹配的数据, 替换成指定的字符串
# regex 正则表达式规则, value 匹配的数据需要替换的内容
df.replace(regex='^ba.$', value='new')
输出
A B
0 new abc
1 foo new
2 bait xyz
# 可以指定字典结合正则进行替换
df.replace(regex={'^ba.$': 'new', 'foo': 'xyz'})
输出
A B
0 new abc
1 xyz new
2 bait xyz
# 多个正则, 整体替换
df.replace(regex=['^ba.$', 'foo'], value='111')
输出
A B
0 111 abc
1 111 111
2 bait xyz
- 案例
df = pd.read_csv('./data/淘宝T恤.csv', engine='python', encoding='gbk')
df
df['location'].replace(regex='.*? ', value='') #广东 广州,匹配空格后面的们就是城市了
4.2 数据转换
- map函数替换
s = pd.Series(['cat', 'dog', np.nan, 'rebbit'])
s
0 cat
1 dog
2 NaN
3 rebbit
dtype: object
# 可以指定字典进行替换, 但是没有指定的内容会当做缺失值处理
s.map({'cat': 'kitten', 'dog': 'bobby'})
输出:
0 kitten
1 bobby
2 NaN
3 NaN
dtype: object
def func(temp):
print('传入的值:', temp)
return f'I am a {temp}'
# 在替换的时候 None 会当做字符串处理
# map指定函数替换, 会将Series中每一个数据传递到函数的参数中处理, 一个一个传递进去
s.map(func)
传入的值: cat
传入的值: dog
传入的值: nan
传入的值: rebbit
0 I am a cat
1 I am a dog
2 I am a nan
3 I am a rebbit
dtype: object
s.map(func, na_action='ignore') # na_action='ignore' 忽略缺失值操作数据
传入的值: cat
传入的值: dog
传入的值: rebbit
0 I am a cat
1 I am a dog
2 NaN
3 I am a rebbit
dtype: object
apply
apply 将函数应用到列或行上
- apply(func, axis=0)
- func:自定义函数
- axis=0:默认是列,axis=1为行进行运算
- 定义一个对列,最大值-最小值的函数
df = pd.DataFrame(data=[[4, 9],
[4, 9],
[4, 9]],
columns=['A', 'B'])
df
输出:
A B
0 4 9
1 4 9
2 4 9
#平方化
def funx(x):
print("传进来的值:\n", x)
return x * x
# apply 默认操作整列
df.apply(funx)
传进来的值:
0 4
1 4
2 4
Name: A, dtype: int64
传进来的值:
0 9
1 9
2 9
Name: B, dtype: int64
A B
0 16 81
1 16 81
2 16 81
def funx_sum(x):
return np.sum(x)
df.apply(funx_sum) #默认操作轴axis=0,按列操作
输出:
A 12
B 27
dtype: int64
df.apply(funx_sum, axis=1)
0 13
1 13
2 13
dtype: int64
applymap
applymap 将函数应用到每个数据
"""批量操作数据 针对每一个元素"""
df = pd.DataFrame([[1, 2.12], [3.356, 4.567]])
print(df)
print(df.applymap(lambda x: '%.2f' % x))
输出:
0 1
0 1.000 2.120
1 3.356 4.567
0 1
0 1.00 2.12
1 3.36 4.57
- Series --> map函数操作
- DataFrame --> apply 可以操作行或者列
- DataFrame每个元素 --> applymap replace
- 案例
df = pd.read_excel('./data/淘宝T恤评论.xlsx')
df
df1 = df[['标题', 'card-act']]
df1
# 获取转发次数,删除"转发"
def func(x):
result = x.split()
# print(result) //['转发','8'] ['转发'] 都是字符串类型
if len(result) == 1:
return 1
else:
return int(result[1])
df1['card-act'] = df1['card-act'].map(func)
df1
#运行一次之后再次运行需要重启内核
# 评论也是如此
for arg in ['card-act', 'card-act1']:
df[arg] = df[arg].map(func)
df
5 字符串操作
5.1字符串方法
5.2 正则表达式方法
5.3 Pandas字符串函数
- 案例
df = pd.read_csv('./data/taobao_data.csv', encoding='gbk')
df
df = df[ ['标题', 'deal-cnt']]
df
#字符串替换
df['deal-cnt'] = df['deal-cnt'].str.strip('人收货') # 字符串替换, 一定要转对象
def func(x):
if '万+' in x:
return float(x.replace('万+', '')) * 10000
elif '+' in x:
return float(x.replace('+', ''))
else:
return float(x)
df['deal-cnt'] = df['deal-cnt'].map(func)
df