实战:再论pandas,正则表达式和xlrd格式化分类并提取多个excel文档数据

前言

陆陆续续又进行了几次数据处理,这些任务都大体沿用了第二次实战的总结过的思路和方法,效率都挺高的。今日再次有了一些新思路,便再次总结一番。

前一版本的不足

第二次实战中有所不足:

  1. 无法处理excel的sheet中出现的同一时间的多份不同地点的统计数据
  2. 需要手动设定行数
  3. 在字符串的正则表达式匹配上有所不足
  4. 无法处理excel多重表头(excel中出现合并单元格之后怎么办?MultiIndex如何处理?)
  5. 没有中间存储的机制(有中间数据先进行保存如果有需要手动检查的会更方便)

Input

输入的表格

文件夹中的excel文件,格式如下:
在这里插入图片描述

Output

其中一行的输入类似:
在这里插入图片描述

对Input和Output的综合分析

表格解读

第一行是表头,可以起到定位作用,因为在同一个sheet中有多个同一时间不同地点的表格(很不规范有没有,干嘛不多建几个sheet)
第二行是调查时间,包括年月日,很好
第三行是调查地点,根据Output的情况可以发现调查地点只接受到街道(乡镇)一级,检测地点是村一级与地址门牌的结合。
第四行是环境类型
第五行~第七行是多重表头,按照xlrd对多重表头的读取,合并后的单元格只有第一次出现的行列是值,其余位置皆为空字符串,例如:“编号”的位置是(5, A),而(6, A)和(7, A)都是空字符串,“植物”的位置是(6, E),而(6, F)的位置是空字符串。多重表头的处理个人认为是个小难点。
第八行~第三十二行是表格内容,根据观察发现表格内容的行数不固定,需要注意漏读的风险。
第三十三行是合计,与表头同样可以起到定位作用
第三十四行是韦恩图指数(乱编的),没什么用
第三十五行是一些注意事项,对于理解表格内容有帮助。
第三十六行是监测单位,监测人和审核人,同时也是一个表格的最后一行。

分析

  1. 采取定位。在同一个sheet的不同表格之间有不等的空行,为了更方便读取数据,我通过定位表头,合计和监测单位三行进行表格内容的读取(第三十五行在某些表格中不存在)。

  2. 在读取调查时间,调查地点,环境类型,监测单位,监测人和审核人数据时因为下划线的原因需要先对数据进行一下替换,可用str.replace函数

  3. 多重表头变单行表格,我通过画图来找映射关系,千万不要小看了可视化的力量。 手绘图如下:
    在这里插入图片描述

  4. 多重表格使得直接读取不太方便,于是我直接手动设置MultiIndex,这样反而快速便捷,而对于Output的单行表格来说,我之前竟然没想到直接读入再将Input赋值即可,反而费劲心思调整Input数据使其符合规范。

部分自定义函数讲解

有部分代码与第二次实战重合,这里不再赘述。

get_invs_place

在进行数据提取前先进行了一些字符串处理,将杂乱的下划线和空格消除。

def get_invs_place(place):
    place = place.replace('_', '')  # 清洗数据可以先用字符串方法处理一波
    place = place.replace(' ', '')
    place += '('
    print(place)
    total = re.findall(r'(?<=[:)])[\u4e00-\u9fa5]+(?=[\s(])', place)  
    print(total)
    base_place = ''
    country = ''
    for i, value in enumerate(total): # 分为调查地点和监测地点两类
        if i < 2:
            base_place += value
        else:
            country += value
    # print(base_place, ' ', country)
    return base_place, country

set_nan

用古老的两层for循环进行的,其实用df.apply+lambda函数来判断空并进行整列替换应该会更高效,之后会进行改进。

def set_nan(df):
    for row in range(df.shape[0]):
        for col in range(df.shape[1]):
            if df.iloc[row, col] == '':
                df.iloc[row, col] = np.nan
    return df

get_corr_indices

用正则表达式来定位并记录index坐标

def get_corr_indices(sheet):  # 定位
    total_line = []  # 标记“合计”所在的行的位置
    unit_line = []  # 标记“监测单位”所在的行的位置
    title_line = []  # 标记“标题”所在的行的位置
    print(sheet.nrows)
    for i in range(sheet.nrows):
        # print(sheet.row_values(i))
        if re.findall('^合计', str(sheet.cell_value(i, 0))) == ['合计']:
            total_line.append(i)
        elif re.findall('^监测单位:', str(sheet.cell_value(i, 0))) == ['监测单位:']:
            unit_line.append(i)
        elif sheet.cell_value(i, 0) == '韦恩图指数法监测记录表':
            title_line.append(i)
    print('total_line: ', total_line)
    print('unit_line: ', unit_line)
    print('title_line: ', title_line)
    return total_line, unit_line, title_line

get_original_content

因为表头已经固定了,所以用这一列先读取原始数据,后面再直接df.loc[]插入新数据

def get_original_content(df, sheet, title_line, total_line):  # 获得原始表格的数据
    for control in range(len(title_line)):
        for i in range(title_line[control] + 7, total_line[control]):
            print(sheet.row_values(i))
            df.loc[i] = sheet.row_values(i)
    return df

以上为文件一


以下为文件二

get_new_df

通过文件一生成的中间值进行“映射”处理

def get_new_df(df):
    work_book = xlrd.open_workbook(r'E:\2018.xlsx')
    sheet = work_book.sheet_by_name('Sheet1')
    new_df = pd.DataFrame([], columns=sheet.row_values(0))
    print(new_df)
    water_type_columns = df.columns[3:21]
    k = 0
    for i in range(df.shape[0]):
        for j in range(0, len(water_type_columns), 2):
        # 赋值操作,看起来复杂其实很简单
            if df.loc[i, water_type_columns[j]] is not '':
                _, water_type, num_water = water_type_columns[j]
                _, _, num_positive = water_type_columns[j + 1]
                new_df.loc[k, '水体类型'] = water_type
                new_df.loc[k, '调查水体数'] = df.loc[i, ('水体类型a', water_type, num_water)]
                new_df.loc[k, '调查水体阳性数'] = df.loc[i, ('水体类型a', water_type, num_positive)]
            else:
                new_df.loc[k, '水体类型'] = ''
                new_df.loc[k, '调查水体数'] = ''
                new_df.loc[k, '调查水体阳性数'] = ''

            new_df.loc[k, '调查日期'] = df.loc[i, ('调查日期', '调查日期', '调查日期')]
            new_df.loc[k, '调查地点'] = df.loc[i, ('调查地点', '调查地点', '调查地点')]
            new_df.loc[k, '环境类型'] = df.loc[i, ('环境类型', '环境类型', '环境类型')]
            new_df.loc[k, '监测地址'] = df.loc[i, ('地址门牌', '地址门牌', '地址门牌')]
            new_df.loc[k, '室内/室外'] = df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')]
            new_df.loc[k, '调查户数'] = df.loc[i, ('户数', '户数', '户数')]
            new_df.loc[k, 'a蚊1是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')]
            new_df.loc[k, 'a蚊2是否存在'] = '不存在'
            new_df.loc[k, '其他a蚊是否存在'] = '不存在'
            new_df.loc[k, 'b蚊是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')]
            new_df.loc[k, 'c蚊是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')]
            new_df.loc[k, '监测单位'] = df.loc[i, ('监测单位', '监测单位', '监测单位')]
            new_df.loc[k, '监测人'] = df.loc[i, ('监测人', '监测人', '监测人')]
            new_df.loc[k, '审核人'] = df.loc[i, ('审核人', '审核人', '审核人')]
            k += 1
            # print(new_df.loc[0, '水体类型'])
    # 去重但保留第一个
    new_df.drop_duplicates(inplace=True)
    # 因为有捉到蚊子的就不需要保留空的,所以这里把上一步去重剩下的有捉到蚊子的其他空行删掉
    duplicated_new_df = new_df.duplicated(subset=['监测地址', '调查日期'], keep=False)
    print(duplicated_new_df.head(20))
    for index in duplicated_new_df.index:
        print(duplicated_new_df.loc[index],new_df.loc[index, '水体类型'])
        if duplicated_new_df.loc[index] == True and new_df.loc[index, '水体类型'] == '':
            new_df.drop(labels=index, inplace=True)
    return new_df

完整代码

将步骤一分为二,文件一是处理老数据的,将老数据第一步整理后输出中间csv(还是多重列表),文件二对中间csv进行进一步处理得到最终结果。
file1.py

import os
import re
import pandas as pd
import numpy as np
import xlrd

# 目标表头:xxx xxx xxx (放这里方便查看)


def get_file_name(base_path):
    file_collection = []
    for dir, subDir, files in os.walk(base_path):
        # print(dir,'\t', subDir,'\t', files)
        for file in files:
            in_path = os.path.join(dir, file)  # 替换 in_path = dir + '/' + file
            if file.endswith('xlsx') or file.endswith('xls'):  # 筛选后缀为.xlsx和.xls的文件
                file_collection.append(in_path)
    return file_collection


def get_invs_place(place):
    place = place.replace('_', '')  # 清洗数据可以先用字符串方法处理一波
    place = place.replace(' ', '')
    place += '('
    print(place)
    total = re.findall(r'(?<=[:)])[\u4e00-\u9fa5]+(?=[\s(])', place)  
    print(total)
    base_place = ''
    country = ''
    for i, value in enumerate(total):
        if i < 2:
            base_place += value
        else:
            country += value
    # print(base_place, ' ', country)
    return base_place, country


def set_df_date(time):
    total = re.findall(r'(?<=[\s_月年日])[\d]+(?=[\s_月日年])', time)  
    print('total = ', total)
    if len(total) == 3:
        return total[0] + '-' + total[1] + '-' + total[2]
    elif len(total) == 2:
        return total[0] + '-' + total[1]
    elif len(total) == 1:
        return total[0]
    return None


def get_unit_Reporter_PeopleInCharge(urp):
    total = re.findall(r'(?<=[\s_:])[\u4e00-\u9fa5a-zA-Z、]+(?=[\s_])', urp)
    # [\u4e00-\u9fa5]代表只匹配汉字
    print(total)

    if len(total) == 3:
        return total[0], total[1], total[2]
    elif len(total) == 2:
        return total[0], total[1], None
    elif len(total) == 1:
        return total[0], None, None
    elif len(total) == 0:
        return None, None, None


def get_env_type(env_type):
    total = re.findall(r'(?<=[\s√✔])[\u4e00-\u9fa5a-zA-Z、:()/_]+(?=[\s;])', env_type)
    # print(total)
    if len(total) == 1:
        return total[0]
    return None

def set_nan(df):
    for row in range(df.shape[0]):
        for col in range(df.shape[1]):
            if df.iloc[row, col] == '':
                df.iloc[row, col] = np.nan
    return df

def get_corr_indices(sheet):  # 定位
    total_line = []  # 标记“合计”所在的行的位置
    unit_line = []  # 标记“监测单位”所在的行的位置
    title_line = []  # 标记“标题”所在的行的位置
    print(sheet.nrows)
    for i in range(sheet.nrows):
        # print(sheet.row_values(i))
        if re.findall('^合计', str(sheet.cell_value(i, 0))) == ['合计']:
            total_line.append(i)
        elif re.findall('^监测单位:', str(sheet.cell_value(i, 0))) == ['监测单位:']:
            unit_line.append(i)
        elif sheet.cell_value(i, 0) == '韦恩图指数法监测记录表':
            title_line.append(i)
    print('total_line: ', total_line)
    print('unit_line: ', unit_line)
    print('title_line: ', title_line)
    return total_line, unit_line, title_line


def get_original_content(df, sheet, title_line, total_line):  # 获得原始表格的数据
    for control in range(len(title_line)):
        for i in range(title_line[control] + 7, total_line[control]):
            print(sheet.row_values(i))
            df.loc[i] = sheet.row_values(i)
    return df

def get_sheet_df(file_name, sheet_name):
    work_book = xlrd.open_workbook(file_name)
    sheet = work_book.sheet_by_name(sheet_name)
    # print(sheet.row_values(col_names)[0])
    df = pd.DataFrame([], columns=[
        ['编号', '地址门牌', '调查地(户内/户外)', '户数'] + ['水体类型a'] * 18 + ['蚊幼种类存在情况b'] * 3,
        ['编号', '地址门牌', '调查地(户内/户外)', '户数'] + ['植物'] * 2 + ['水池、缸、盆、桶、坛、槽'] * 2 + ['容器(碗、瓶、缸、罐)'] * 2 + [
            '明渠、假山'] * 2 + ['竹头、树洞、石穴'] * 2 + ['轮胎、废旧轮胎'] * 2 + ['绿化带垃圾、可存水废弃物'] * 2 + ['地下室及停车场'] * 2 + [
            '其他水体'] * 2 + ['a蚊c', 'b蚊', 'c蚊'],
        ['编号', '地址门牌', '调查地(户内/户外)', '户数'] + ['积水数', 'positive'] * 9 + ['a蚊c', 'b蚊', 'c蚊']
    ])

    total_line, unit_line, title_line = get_corr_indices(sheet)
    df = get_original_content(df, sheet, title_line, total_line)

    for control in range(len(title_line)):
        for i in range(title_line[control] + 7, total_line[control]):

            time = set_df_date(sheet.cell_value(title_line[control] + 1, 0))
            place, country = get_invs_place(sheet.cell_value(title_line[control] + 2, 0))
            env_type = get_env_type(sheet.cell_value(title_line[control] + 3, 0))

            print(control)
            unit, reporter, PeopleInCharge = '', '', ''
            if control >= 0:
                unit, reporter, PeopleInCharge = get_unit_Reporter_PeopleInCharge(
                    sheet.cell_value(unit_line[control], 0))

            print(time)
            df.loc[i, ('调查日期', '调查日期', '调查日期')] = time
            df.loc[i, ('调查地点', '调查地点', '调查地点')] = place

            df.loc[i, ('地址门牌', '地址门牌', '地址门牌')] = country + str(df.loc[i, ('地址门牌', '地址门牌', '地址门牌')])
            df.loc[i, ('环境类型', '环境类型', '环境类型')] = env_type
            df.loc[i, ('监测单位', '监测单位', '监测单位')] = unit
            df.loc[i, ('监测人', '监测人', '监测人')] = reporter
            df.loc[i, ('审核人', '审核人', '审核人')] = PeopleInCharge

            if df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')] == '户内':
                df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')] = '室内'
            elif df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')] == '户外':
                df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')] = '室外'

            if df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] == '存在' or df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] == '✔' or \
                    df.loc[
                        i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] == '√' or df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] == 1 or \
                    df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] == 2:
                df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] = '存在'
            else:
                df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')] = '不存在'
                
            if df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')] == '存在' or df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')] == '✔' or df.loc[
                i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')] == '√':
                df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')] = '存在'
            else:
                df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')] = '不存在'

            if df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')] == '存在' or df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')] == '✔' or df.loc[
                i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')] == '√':
                df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')] = '存在'
            else:
                df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')] = '不存在'

            if sheet.cell_value(i, 0) == '布雷图指数法监测记录表':
                control += 1

    if df.empty:  # 如果列表为空直接返回
        return df

    df.drop([('编号', '编号', '编号')], axis=1, inplace=True)  # 丢掉没用的列,axis=1代表[row,col]的col
    df.drop_duplicates(keep=False, inplace=True)
    df = set_nan(df)  # 把所有的''设为NaN,方便dropna
    df.dropna(how='all', axis=0, inplace=True)
    df.reset_index(drop=True, inplace=True)  # 重设索引,drop=True表示丢掉原本的索引
    return df

def save_file(df, path):
    df.to_csv(path + '\\' + '汇总.csv', encoding='gbk', index=False)  # 保存为csv

if __name__ == '__main__':
    base_paths = [r'E:\2018']
    areas = ['图']  
    work_book = xlrd.open_workbook(r'E:\abc.xlsx')
    sheet = work_book.sheet_by_name('Sheet1')
    order = sheet.row_values(rowx=0)
    file_collection = get_file_name(base_paths[0])
    for file in file_collection:
        print(file)

    df = [pd.DataFrame([])]
    for file in file_collection:
        for area in areas:
            df[0] = df[0].append(get_sheet_df(file_name=file, sheet_name=area)) 

    df[0].reset_index(drop=True, inplace=True)  # 重设索引,drop=True表示丢掉原本的索引
    save_file(df=df[0], path=base_paths[0])


file2.py

import os
import re
import pandas as pd
import numpy as np
import xlrd


def get_new_df(df):
    work_book = xlrd.open_workbook(r'E:\2018.xlsx')
    sheet = work_book.sheet_by_name('Sheet1')
    new_df = pd.DataFrame([], columns=sheet.row_values(0))
    print(new_df)
    water_type_columns = df.columns[3:21]
    k = 0
    for i in range(df.shape[0]):
        for j in range(0, len(water_type_columns), 2):
            if df.loc[i, water_type_columns[j]] is not '':
                _, water_type, num_water = water_type_columns[j]
                _, _, num_positive = water_type_columns[j + 1]
                new_df.loc[k, '水体类型'] = water_type
                new_df.loc[k, '调查水体数'] = df.loc[i, ('水体类型a', water_type, num_water)]
                new_df.loc[k, '调查水体阳性数'] = df.loc[i, ('水体类型a', water_type, num_positive)]
            else:
                new_df.loc[k, '水体类型'] = ''
                new_df.loc[k, '调查水体数'] = ''
                new_df.loc[k, '调查水体阳性数'] = ''

            new_df.loc[k, '调查日期'] = df.loc[i, ('调查日期', '调查日期', '调查日期')]
            new_df.loc[k, '调查地点'] = df.loc[i, ('调查地点', '调查地点', '调查地点')]
            new_df.loc[k, '环境类型'] = df.loc[i, ('环境类型', '环境类型', '环境类型')]
            new_df.loc[k, '监测地址'] = df.loc[i, ('地址门牌', '地址门牌', '地址门牌')]
            new_df.loc[k, '室内/室外'] = df.loc[i, ('调查地(户内/户外)', '调查地(户内/户外)', '调查地(户内/户外)')]
            new_df.loc[k, '调查户数'] = df.loc[i, ('户数', '户数', '户数')]
            new_df.loc[k, 'a蚊1是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'a蚊c', 'a蚊c')]
            new_df.loc[k, 'a蚊2是否存在'] = '不存在'
            new_df.loc[k, '其他a蚊是否存在'] = '不存在'
            new_df.loc[k, 'b蚊是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'b蚊', 'b蚊')]
            new_df.loc[k, 'c蚊是否存在'] = df.loc[i, ('蚊幼种类存在情况b', 'c蚊', 'c蚊')]
            new_df.loc[k, '监测单位'] = df.loc[i, ('监测单位', '监测单位', '监测单位')]
            new_df.loc[k, '监测人'] = df.loc[i, ('监测人', '监测人', '监测人')]
            new_df.loc[k, '审核人'] = df.loc[i, ('审核人', '审核人', '审核人')]
            k += 1
            # print(new_df.loc[0, '水体类型'])
    new_df.drop_duplicates(inplace=True)
    duplicated_new_df = new_df.duplicated(subset=['监测地址', '调查日期'], keep=False)
    print(duplicated_new_df.head(20))
    for index in duplicated_new_df.index:
        print(duplicated_new_df.loc[index],new_df.loc[index, '水体类型'])
        if duplicated_new_df.loc[index] == True and new_df.loc[index, '水体类型'] == '':
            new_df.drop(labels=index, inplace=True)
    return new_df


def save_file(df, path):
    df.to_csv(path + '\\' + '新汇总.csv', encoding='gbk', index=False)  # 保存为csv


df = pd.read_csv(r'E:\汇总.csv', encoding='gbk', header=[0, 1, 2], keep_default_na=False)
print(df)
df = get_new_df(df)
print(df)
save_file(df, r'E:\2018')

总结

这次任务耗时近两天,之前没有通过可视化的方式画出映射,在此上花费太多时间。在结构方面需要更加灵活,而不是死板地设置表格长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值