Pandas数据清洗

数据清洗介绍

数据清洗实际上也是数据质量分析,检查原始数据中是否存在脏数据(不符合要求,或者不能直接进行分析的数据),并且处理脏数据。

常见情况如下

  • 缺失值
  • 异常值
  • 重复数据


处理缺失值

Pandas使用浮点值NaN(not a Number)表示缺失值,并且缺失值在数据中时常出现。那么Pandas的目的之一就是“无痛地”处理缺失值。

判断数据是否为NaN

  • pd.isnull(df) 返回哪些值是缺失值的布尔值
  • pd.notnull(df) 返回值是isnull的反集

注意

  • Python内建的None值也被当作NaN

过滤缺失值

  • dropna(axis=0,how=’any’,inplace=False)
    • axis 指定轴 默认为0 代表行
    • how 默认为any 代表删除含有NaN的行 当为all 时代表删除所有值为NaN的行
    • inplace 修改被调用的对象 而不是一个备份


  1. # Series 过滤缺失值
  2. ds = pd.Series([1, np.nan, 2, 3, 4, np.nan])
  3. # 传统的过滤方法,布尔索引过滤
  4. # ds[ds.notnull()]
  5. ds.dropna() # dropna()过滤缺失值
  6. # 输出结果, 含有NaN的那行就删掉了
  7. 0 1.0
  8. 2 2.0
  9. 3 3.0
  10. 4 4.0
  11. dtype: float64
  1. # DataFrame过滤缺失值
  2. df = pd.DataFrame(np.arange(16).reshape(4,4))
  3. df.iloc[1, :] = np.nan
  4. df.iloc[3,1] = np.nan
  5. # df.dropna() # 默认是删除含有NAN的行
  6. df.dropna(axis=1) # 指定含有NAN的列
  7. # 输出结果
  8. 0 1 2 3
  9. 0 0.0 1.0 2.0 3.0
  10. 2 8.0 9.0 10.0 11.0


补全缺失值(NaN)

  • df.fillna(value=None,method=None,axis=None,inplace=False,limit=None)
    • value 标量或字典对象用于填充缺失值
    • method 插值方法 默认为”ffill”
    • axis 需填充的轴 默认为0
    • inplace 修改被调用的对象 而不是一个备份
    • limit 用于向前或向后填充时最大的填充范围 ```python “”” Series填充,标量直接填充 “”” ds = pd.Series([1, np.nan, 2, 3, 4, np.nan]) ds.fillna(2)

输出结果

0 1.0 1 2.0 2 2.0 3 3.0 4 4.0 5 2.0 dtype: float64

  1. <a name="KL1Hw"></a>
  2. ###
  3. ```python
  4. """
  5. DataFrame数据填充
  6. """
  7. df = pd.DataFrame(np.arange(12).reshape(3,4), index=list("ABC"), columns=list("WXYZ"))
  8. df.loc["B",["X","Y"]] = np.nan
  9. df.loc["C",["X","Y"]] = np.nan
  10. # df.fillna(2) #默认情况下所有NAN填充相同标量值
  11. # df.fillna({"X":3, "Y":4}) # value值可以为字典,这里的意思就是X列的NAN填充为3,Y列填充为4
  12. df.fillna(method="ffill") # method参数为往上插补,默认是ffill; 功能类似于EXCEL单元格向下拉动
  13. df.fillna(method="ffill", limit=1) # limit参数可以限制插补次数
  14. df.fillna(df.mean()) #填充平均值或者中位数

异常值

脏数据也包含不符合要求的数据,那么对这块数据处理不能直接使用fillna填充。使用replace更加灵活。

  • df.replace(to_replace=None,value=None)
    • to_replace 去替换的值
    • value 替换的值 ```python import numpy as np import pandas as pd

df = pd.DataFrame({“age”:[-1,20,19,27]})

df.replace(-1, df.median()) # 将-1替换为中值

df.replace([-1, 27], 23) # 一次性替换多个值

df.replace([-1, 27], [22, 23]) # 不同值替换不同的值

df.replace({-1:22, 27:23}) # 传入字典

> **注意: df.replace()是字符串方法, 如果要实现判断某个值的大小然后再替换,得要用df.where()**



- **df.where(cond, other=nan, inplace=False, axis=None, level=None, errors='raise', try_cast=False)**
   - cond:判断条件,判断为真,值不变;判断为假,替换other的值
   - other:需要替换的值,默认为NaA
   - inplace 修改被调用的对象 而不是一个备份
   - axis 需填充的轴 默认为0

<br />
```python
df.where((df<20, 23)
df.where((df>0) & (df<20), 23)

处理重复数据

判断重复值

  • df.duplicated(subset=None, keep=’first’) 返回的一个布尔值Series 默认反映的是每一行是否与之前出现过的行相同
    • subset 指定子列判断重复
    • keep 默认为first保留首个出现的 last保留最后出现的


df2 = pd.DataFrame({
    "age":[18,19,18,20],
    "gender": ["male","female"] * 2,
    "name": ["amy", "connor", "amy", "ben"]
})

"""
判断每一行是否有重复
"""
df2.duplicated()  # 索引为2的与0重复, 输出布尔值

df2.duplicated(subset=["gender", "name"])  # 使用subset参数,根据gender和name两列判断重复

df2.duplicated(subset=["gender", "name"], keep="last") # keep参数默认是first,保留第一个;如果为last,则第一条为重复值,会标为True


删除重复值

  • df.drop_duplicates() 返回的是DataFrame 默认删除重复行
    • subset 指定的数据任何子集是否有重复
    • keep 默认为first保留首个出现的 last保留最后出现的


# df2.drop_duplicates()  # 删除重复行,保留第一行

# df2.drop_duplicates(subset=["gender","name"])  # 使用subset参数,根据gender和name两列判断重复值并删除行

# df2.drop_duplicates(subset=["gender","name"], keep="last")  # 保留最后一个,删除前面的

离散化

离散化是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
可以简单的理解为离散化就是将连续值进行分区间

  • pd.cut(x,bins) 将连续数据x进行离散化
    • x 要进行离散化的数据
    • bins 分组


  • pd.value_counts(cates) 统计每个区间的数值分布 ```python import numpy as np import pandas as pd

age_arr = np.random.randint(20, 60, size=20)# 创建数组 age_arr

<a name="bvYJ1"></a>
### 
```python
"""
以10岁为一个区间, 如20~30, 30~40, 40~50
"""
bins = [20,30,40,50,60]  # 定义区间规则
cates = pd.cut(age_arr, bins)  # 输出数组中每个值所在的区间
cates

#输出结果
[(30, 40], (40, 50], (50, 60], (50, 60], (40, 50], ..., (30, 40], (20, 30], (40, 50], (40, 50], (50, 60]]
Length: 20
Categories (4, interval[int64]): [(20, 30] < (30, 40] < (40, 50] < (50, 60]]

"""
codes 输出数据分组所在区间的组的索引

如果以区间分组,可以分为4组: 20~30, 30~40, 40~50,50~60,索引也是0、1、2、3 因此codes就是返回数据所在组的索引
"""
cates.codes  # 返回数据分布所在组的索引

# 输出结果
array([1, 2, 3, 3, 2, 2, 1, 1, 2, 0, 3, 0, 1, 1, 0, 1, 0, 2, 2, 3],
      dtype=int8)
"""
categories 输出分组的情况

closed='right',区间是右闭合
"""
cates.categories

# 输出结果
IntervalIndex([(20, 30], (30, 40], (40, 50], (50, 60]],
              closed='right',
              dtype='interval[int64]')

"""
自定义区间标签名称
"""
l_name = ["youth", "youngAdult", "MiddleAged", "Old"]  # 定义标签名称
cates = pd.cut(age_arr, bins, labels=l_name)
cates

# 输出结果
['youngAdult', 'MiddleAged', 'Old', 'Old', 'MiddleAged', ..., 'youngAdult', 'youth', 'MiddleAged', 'MiddleAged', 'Old']
Length: 20
Categories (4, object): ['youth' < 'youngAdult' < 'MiddleAged' < 'Old']
"""
统计每个区间的数值分布
"""
pd.value_counts(cates)

#输出结果
MiddleAged    6
youngAdult    6
Old           4
youth         4
dtype: int64

计算虚拟变量

分类变量转换为”虚拟”或”指标”矩阵是另一种用于统计建模或机器学习的转换操作。如果DataFrame中的一列有k个不同的值,则可以衍生一个K列的值为1和0的矩阵或DataFrame。

  • pd.get_dummies() 将分类变量转换为”虚拟”或”指标”矩阵 ```python import numpy as np import pandas as pd

df = pd.DataFrame({ “key”:[“b”,”b”,”c”,”d”,”a”,”d”], “datal”:range(6) }) df

输出结果

key    datal

0 b 0 1 b 1 2 c 2 3 d 3 4 a 4 5 d 5

```python
"""
将key列转换为虚拟矩阵
"""
pd.get_dummies(df["key"])  # 将key列传入

pd.get_dummies(df["key"], prefix="key")  # prefix可以添加前缀

# 输出结果

   key_a    key_b    key_c    key_d
0     0          1          0          0
1     0          1          0          0
2     0          0          1          0
3     0          0          0          1
4     1          0          0          0
5     0          0          0          1

但是,如果说DataFrame中的一行属于多个类别,情况就会比较复杂。如下图
image.png
举例:

"""
读取IMDB-Movie-Data.csv对其分类(Genre)这列转化为虚拟矩阵

- 重新构建一个全为0的数组, 列:分类 行:原数组的行一致
- 如果某一条数据中的分类出现过, 赋值为1
"""
df = pd.read_csv("IMDB-Movie-Data.csv")
# df.head()

"""
1、构建分类列表
"""
all_genres = []
for x in df.Genre: # 获取某一列的值除了使用“[]”,也可以使用"."
#     print(x)
    l = x.split(",")
    all_genres.extend(l)  # 将所有值都放入一个列表中,然后等待去重

"""
去重方式有很多,例如:
- set()
- drop_duplicates
- unique
"""
genres = pd.unique(all_genres)  # 使用unique去重

"""
2、构建全为0的数组
"""
zero_data = np.zeros((len(df), len(genres)) )  # 生成全为0的数组, 传入格式是元组, 输出array数组
zero_data = pd.DataFrame(zero_data, columns=genres)  # array数组转为DataFrame数组
zero_data.head()

"""
3、如果某一条数据中的分类出现过, 赋值为1
- 选择数据(位置索引)
- 行索引: df.Genre定位到多少行, 行索引就是多少行
- 列索引: 获取到Genre里面每个元素所在的列索引的位置
"""
# 以Genre第一个数据为例
# df.columns.get_indexer()

f_data = df.Genre[0]
# print(f_data.split(","))  # 输出['Action', 'Adventure', 'Sci-Fi']
zero_data.columns.get_indexer(f_data.split(","))  # 获取到f_data每一个值的位置,也就是列索引的位置


for i,genre in enumerate(df.Genre): #遍历获取df.Genre中每一个元素以及对应的索引
    col_index = zero_data.columns.get_indexer(genre.split(","))
#     print(col_index) 

    # 通过位置索引选择数据并且赋值为1
    zero_data.iloc[i,col_index] = 1  # zero_data.iloc[0, [0, 1, 2]]

zero_data.head()