Pandas 清除 Excel 特殊字符

时间:2024-02-18 20:18:14

清除 Excel 特殊字符

主要是为了做一个笔记, 用 遍历 DataFrame 用正则匹配特殊字符并替换.

是上个月初的项目了, 其中有个将 Excel 传入数据库的时候, 发现有特殊字符, 很奇怪的那种特殊字符, 什么小五角星, 小书书, 小太阳, 耳机 等这种字符, 真的好奇怪. 后来发现, 这种问题竟然是经常出现, 于是来做个笔记, 方便自己以后再遇到的时候, 能轻松复制粘贴.

需求

清除 Excel 的特殊字符, 诸如 小星星, 小花花, 小太阳, 小提琴, 小耳机 ... 等这些小可爱.

输入是一个 Excel 表格, 输出去 清除 (替换) 掉特殊字符的 Excel 表格 ( 跟输入一样) .

方案

  • 特殊字符匹配用 正则表达式在查询并替换 re.compile(); re.sub();

  • 用 Pandas 读取 Excel 数据为 DataFrame

  • 遍历 DataFrame 的每个格子, 进行正则匹配 并替换 df.loc 和 iloc

查找特殊字符并替换

找了好久的规律, 仔细琢磨发现的, 那些特殊字符是可用 unicode 进行匹配出来.

[\U0001000 - \U001ffff]

然后呢, 我们的操作就是, 查找, 并并进行替换, 封装为一个函数哦.

def replace_spec_char(char, replace_char):
    """替换掉特殊字符, 用某种自定义标记"""
    
    if not isinstance(char, str):
        return "输入的是非字符串哦"
    
    pattern = re.compile(u\'[\U0001000 - \U001ffff]\')
    # 替换
    return pattern.sub( replace_char, char)
    

遍历 DataFrame 并清理

用下标索引 iloc 的方式来弄即可. 其实关于 df.loc 和 df.iloc 我从来就没有真正记住和区分过, 大约只是记得 loc 用于有名字的 所有, 如字段啥的. iloc 是按照下标索引.

而我想表达的学习方法是, 根本不用去刻意记, 有印象和临时百度就好啦. 就好比, 我始终不能区分 json.loads() 和 json.dumps() ; 或者 eval() 和 __ repr __ () ; 或者 pandas / numpy 中 的 轴 axis = 1 或 0 ... 这类到底是啥的问题,

解决之道 是 留大概印象, 即用即查.

def parse_data(file_path, replace_char):
    """替换 Excel 特殊字符"""
    try:
        df = pd.read_excel(file_path):
    except:
        raise "数据读取异常"
    # 遍历每个单元格, 先行后列我比较习惯而已
    row, col = df.shape
    for i in range(row):
        for j in range(col):
            # 当前格元素, 发现上面写不对, 字符校验应该在这里
            cur_value = df.iloc[i, j]
            pure_char = replace_char(cur_value, replace_char)
            # 把特殊字符, 及其所在的行列坐标给打印出来
            if cur_value != pure_chare:
                print(f"特殊字符: {cur_value} 位于 {i+1}行, {j+1}列.")
                # 并同时将当前值用 清理好的字符替换
                df.iloc[i, j] = pure_char
                
    print("清理完毕!")
    

遍历DF 的 cell

还是稍微演示一把好一点.

df = pd.DataFrame({
    \'name\':[\'youyou\', \'youge\', \'jieer\'],
    \'age\': [18, 22, nan],
    \'gender\': [\'F\', nan, \'M\']
})

df
	name	age	  gender
0	youyou	18.0	F
1	youge	22.0	NaN
2	jieer	NaN	     M
# 我个人喜欢按 row 来进行遍历哦
row, col = shape
for i in range(row):
    for j in range(col):
        print(df.iloc[i, j]ue,\'\t\t\', type(df.iloc[i, j]))
youyou 		 <class \'str\'>
18.0 		 <class \'numpy.float64\'>
F 		     <class \'str\'>
youge 		 <class \'str\'>
22.0 		 <class \'numpy.float64\'>
nan 		 <class \'float\'>
jieer 		 <class \'str\'>
nan 		 <class \'numpy.float64\'>
M 		     <class \'str\'>
for i in range(df.shape[0]):
    for j in range(df.shape[1]):
        cur_value = df.iloc[i, j]
        if isinstance(cur_value, str):
            df.iloc[i, j] = "是字符"

# df.iloc[i, j] = \'xxx\' 是原地的哦            
print(df)
    name   age	  gender
0	是字符  18.0   是字符
1	是字符	 22.0	NaN
2	是字符	 NaN	是字符

完整实现

上面的代码呢, 后面的函数, 嵌套了前面的函数. 我之前觉得没啥, 后来跟小伙伴, 他做 Java 的, 然后聊了一波 面向对象, 同时也是, 之前接了一波同事的 数据处理代码, Python 的面向对象写法. 我发现, Python 的面向对象写法, 其实不太好. 一不小心就, 容易, 代码给 混在一起, 互相调用, 容易搞成 高耦合., 别人很难去维护.

至少是脚本这块哈, 我希望自己是:

可能更倾向于, 函数式编程的模式;
或者说, 一个函数一个功能, 尽可能解耦, 最后用 main 来调用.

从本例, 就尽量,不要出现, 第二个 一个函数中, 调用 另一个函数 的风格. 这种可以写在 main 中, 就这样吧. 重在理解过程和让代码可读性更强一些.

def replace_spec_char(char, replace_char):
    """替换掉特殊字符, 用某种自定义标记"""
      # 匹配并进行替换
    pattern = re.compile(u\'[\U0001000 - \U001ffff]\')
    return pattern.sub( replace_char, char)
    
def get_data(file_path):
    """将 Excel 数据读入为 DataFrame"""
    try:
        df = pd.read_excel(file_path)
    except:
        raise "数据读取异常!"
    return df


def main():
    data = get_data(file_path)
    # 获取data 的行, 列数, 并进行遍历
    row, col = data.shape
    # 遍历每个 cell, 并对字符串的 cell 进行清洗
    for i in range(row):
        for j in range(col):
            # cell 值
            cur_value = data.iloc[i, j]  
            # 只对字符如进行清洗
            
            if isinstance(cur_value, str):
                pure_char = replace_spec_char(char, replace_char)
                
                # 跟清洗前做比对, 打印出特殊符行列号
                if cur_value != pure_char:
                    print(f"特殊字符: {cur_value} 位于 {i+1}行, {j+1}列.")
                    
                    # 同时将当前个用清理好的进行原地替换
                    data.iloc[i, j] = pure_char
                    
    print("清理完毕")

因为我经常会用到, 因此还打了个包, exe 的, 哎呀不写了, 打包用 pyinstaller 又不难的, 不展开了.

小结

  • 匹配特殊字符, 结合强大的正则表达式, 如 re.compile(), re.sub(), re.match(), re.findall ( ) 都很常用
  • 遍历 DataFrame 用索引方式 df.iloc 速度还行. 遍历这块, 同样 df.iterrows() 也是我常用的呢
  • 原地替换 data.iloc[i, j] = 666 简单又暴力, 现脚本这块更倾向函数式编程, 尤其是解耦和可读性, 我觉得更重要