Pandas5(数据清洗1)——缺失值处理、数据去重/转换/替换、离散化/分箱、检测和过滤异常值
数据清洗1
处理缺失数据
dropna()
: 根据各标签的值中是否存在缺失数据,对轴标签进行过滤,可通过阈值调节对缺失值的容忍度
ignore_index = True
会忽略原先的索引,生成新的索引;不加 ignore_index 将缺失值进行过滤但是行标签保留下来
how="all"
将只丢弃全为NA的那些行; 默认how="any"
只有包含NA的行都丢弃
thresh=N
至少有N个非缺失值的列/行 才保留数据
inplace=False
是否修改原先的dataframefillna()/bfill()/ffill()
: 用指定值或插值方法(如ffill或bfill)填充缺失数据isnull()/isna()
: 对于非NA值返回False, 对于NA值返回Truenotnull()/notna()
: isna的否定式,对于非NA值返回True, 对于NA值返回False
# 随堂练习 · 智能手环一周健康数据清洗
# 熟悉dropna / fillna / ffill / bfill / isna / notna# 模拟一周数据
# steps: 步数(千步)
# kcal: 消耗卡路里(百卡)
# sleep: 睡眠时长(小时)data = {"day" : ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],"steps": [8.2, 9.1, np.nan, 7.5, 8.8, np.nan, 10.0],"kcal" : [2.3, np.nan,2.6, 2.1, np.nan,3.0, 3.2],"sleep": [7.0, 6.5, 6.0, np.nan,7.8, 8.0, np.nan]
}
df = pd.DataFrame(data).set_index("day")
print("原始数据:\n", df, "\n")# 任务 1 缺失值检测
"""
1.a 用 isna 统计每列缺失值数量
1.b 找出“没有缺失值得行”的日期 (拿到没有缺失值行的索引对象)提示:用 notna 构造布尔 DataFrame,再用 .all(axis=1)
"""
### TODO 1.a
df.isna().sum() # true表示1,false表示0,用sum方法计算true的值
### TODO 1.b
df[df.notna().all(axis = "columns")]# 任务 2 删除缺失 & 阈值容忍
"""
2.a 删除“任何含有NaN的列”
2.b 保留 ≥ 2 个非缺失值的行, 提示:删除缺失值方法的thresh参数
"""
### TODO 2.a
df.dropna(axis="columns", how="any")
### TODO 2.b
df.dropna(axis="index",thresh=3 )# 任务 3 缺失值填补
"""
3.a 对 steps 列用向前填充 ffill
3.b 对 kcal 列用列均值填充
3.c 对 sleep 列用向后填充 bfill,再对剩余 NaN 使用 0 替代
最终将结果合并到 new_df 并打印
"""
## TODO 3
new_df = df.copy()
print(new_df)
new_df["steps"] = new_df["steps"].ffill()
new_df["kcal"] = new_df["kcal"].fillna(df["kcal"].mean())
new_df["sleep"].bfill(inplace=True)
new_df.fillna(0,inplace = True)
print(new_df)# 任务 4 一步完成多列不同策略填充
"""
使用 DataFrame.fillna(dict):- steps: 前向填充- kcal : 均值- sleep: 0
验证结果与任务 3 相同
提示:fillna可以传字典,字典的键是列名,字典的值是 series/数字(表示这一列缺失值要填充成什么)
{"steps": df["steps"].ffill(),"kcal" : df["kcal"].mean(),"sleep": 0
}
"""
### TODO 4
data = {"steps": df["steps"].ffill(),"kcal" : df["kcal"].mean(),"sleep": 0
}
df.fillna(data)
数据转换:去重,转换,替换
duplicated()
:标记每个元素是否重复出现 (即之前已经出现过,出现第一次标记为False,出现第二次及以上的标记为True)- keep=‘first’ (默认): 除了第一个,后面重复的都标记为True
- keep=‘last’: 除了最后一个,前面重复的都标记为True
- keep=False: 所有重复值都标记为True
drop_duplicates()
: 只保留了duplicated()返回结果为False 的dataframe数据。(相当于data.loc[~data.duplicated()],先去重在否定在筛选 )- subset 指定某列进行筛选,去重
- keep=“last”,则保留最后一个 ; keep=False, 丢掉全部重复过的
map()
: 除了可以接收一个函数,也可以接收一个含有映射关系的字典型对象 字典的键->值 来实现键值转换replace()
:map的简单版
# 模拟一份学生花名册
df = pd.DataFrame({"stu_id": [1001,1002,1003,1002,1004,1005,1005],"name" : ["张三","李四","王五","李四","赵六","钱七","钱七"],"major" : ["CS","EE","ME","EE","CS","cs","ME"],"grade" : ["A","B","C","B","A","A","B"]
})
print("原始数据:\n", df)# TODO 1 使用 duplicated 标记出完全重复的行
dup_flags = df.duplicated()# TODO 2 删除完全重复的行,只保留第一次出现
# 提示:为了避免后面的警告,对删除重复行的dataframe来一次复制 (copy方法),因为系统不理解链式索引是视图还是复制的
df_unique = df.drop_duplicates().copy()# TODO 3 用 map 将 grade 列映射为 GPA 分数 (A=4,B=3,C=2), 并添加到一列 GP到df_unique, 来存分数
GPA = {'A' :'4','B' :'3','C' :'2',
}
df["GPA"] = df["grade"].map(GPA)# TODO 4 把 major 列大小写混杂的 "cs" 统一改写为 "CS"(用 replace)
df["major"] = df["major"].replace("cs", "CS")
print("\n清洗后:\n", df)
重命名轴索引
- 索引对象的map方法:通过函数或字典映射进行转换,从而得到一个新的索引对象
rename()
: 对列索引或行索引重命名,data frame内的方法。
使用rename,则无须手动复制DataFrame 并给index和columns属性赋新值
data = pd.DataFrame(np.arange(12).reshape((3,4)),index=["Ohio", "Colorado", "New York"],columns=["one", "two", "three", "four"])
data# 定义一个转换函数
def transform(x):return x[:4].upper()
# 将数据的行索引的字母转换成大写
data.index = data.index.map(transform)# 如果要创建数据集转换后的版本,并且不修改原始数据,比较使用的方法是rename
data = pd.DataFrame(np.arange(12).reshape((3,4)),
index=["Ohio", "Colorado", "New York"],
columns=["one", "two", "three", "four"])
data.rename(index={"OHIO": "INDIANA"}, columns={"three":"三"})
离散化和分箱
-
为了便于分析,连续数据常常被离散化或拆分为"箱子", 各个数据处于某个箱子中。
-
pd.cut()
: 用于将数据分成指定区间的箱(bin),每个箱的宽度(区间范围)可以自定义。生成了 pandas 的特殊分类对象(Categorical),该对象包含codes代码类别 \categories区间索引 \value_counts()属性和方法- 参数:x:要分箱的一维数组或 Series。
- bins:定义分箱的边界(可以是一个整数或列表)。
- 如果是整数:表示将数据分成多少个等宽的区间。
- 如果是列表:表示自定义的区间边界(如 [0, 10, 20, 30])。
- labels:为每个箱指定标签(可选)。
- right:是否包含右边界(默认为 True,即区间为左开右闭 (a, b])。i
- include_lowest:是否包含最小值(默认为 False)
-
pd.qcut()
: 用于将数据分成指定数量的箱,但每个箱中的样本量大致相同(基于分位数)。- 参数:x:要分箱的一维数组或 Series。
- q:分箱的数量(如 4 表示 4 分位数)或自定义分位数列表(如 [0, 0.3, 0.7, 1])。
- labels:为每个箱指定标签(可选)。
- precision:该参数设置为 2 ,表示限定小数点后只有两位有效数字
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] bins = [18, 25, 35, 60, np.inf] # 18-25,25-35,35-60,60-无穷大 age_categories = pd.cut(ages, bins,right = False) age_categories.codes # 类别代码:0,1,2 ... age_categories.categories # 区间索引 age_categories.categories[0] # 区间值,close = right表示右边是闭区间, 这个是pandas专门的区间对象 age_categories.value_counts() # 最后得到一个series,其标签是具体的区间group_names = ["少年", "青年", "中年", "老年"] pd.cut(ages, bins, labels=group_names)data = np.random.standard_normal(1000) quartiles = pd.qcut(data, 4, precision=2) # 根据分位数 均分了数据 0-25%, 25%-50%, 50%-75%, 75%--100% 保留两位有效数字 pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]) # 0-10%, 10%-50%, 50%-90%, 90%-100%
检测和过滤异常值
1、 通过 data.describe()
或者可视化分析图 快速查看数据的分布情况,识别可能的异常值
2、过滤异常值:直接删除,替换为边界值,替换为中位数/均值
# 过滤或转换异常值很大程度上就是运用数组运算。
# 首先查看统计数据
data = pd.DataFrame(np.random.standard_normal((1000,4)))
data.describe()# 假设想要找出某列中绝对值大小超过3的值, (给出一个想要的异常值,偏离平均值3倍标准差的值)
# 假设异常值
col = data[2]
col.loc[col.abs() > 3]# 选出全部含有 超过3或-3的值 的行,可以在布尔型DataFrame中使用any方法
# 将异常值筛选出
data[(data.abs() > 3).any(axis="columns")] # 在看每一行是否有异常值,并将数据进行筛选出来# 替换为边界值
# 下面的代码可以将绝对值超过3的值 赋值为-3或3,将所有制限制在-3--3之间
# np.sign() 返回数据的符号,正数为1,负数为-1
data[data.abs()>=3] = np.sign(data) * 3
data.describe()