实战:运用pandas,正则表达式(re),xlrd,进行多个excel数据汇总,分类

实战:运用pandas,正则表达式(re),xlrd,进行多个excel数据汇总,分类

前言

实习中的第二次实战,相比于第一次处理数据,这一次我更清晰地划分了函数功能,使得开发过程比第一次更快速了(比上次快了半天),尽管还是花了一天半(捂脸)。
第一次实战总结

Input

输入表格

文件夹中的excel文件,格式如下:

蝇类监测记录表(笼诱法)
调查日期:2030 年 4 月
调查地点: YY 省(自治区、直辖市) XX 地(市) ZZZ 县(区)
气温: 12.0 ℃; 风力: 3 级; 天气:晴√ 多云□ 阴□
诱饵种类:规定诱饵√; 其他□:
序号 乡镇(街道) 日期 环境类型 监测地点 布笼数 苍蝇品种1 苍蝇品种2 苍蝇品种3 苍蝇品种4 …… 苍蝇品种10 其他 合计 蝇种列合计 备注
1 a村 1.29 农贸市场 第一百菜市场 1 1 …… 1 1
2 a村 1.29 居民区 第90中学宿舍 1 …… 0 0
3 a村 1.29 绿化带 自然生态公园 1 …… 0 0
…… ……
8 a村 1.29 餐饮外环境 最好吃餐厅 1 0 …… 0 0
9 ……
10 ……
11 ……
…… ……
填报单位: zz区天气预报中心 填报人: Sky 审核人:John

Output

调查日期 调查地点 环境类型 诱饵 监测地点 苍蝇品种1 苍蝇品种2 苍蝇品种3 苍蝇品种4 …… 苍蝇品种10 苍蝇品种11 苍蝇品种12 其他 经度 纬度 气温(℃) 风力(级) 天气 笼编号 监测单位 监测人 审核人 备注
2016/1/7 福建省福州市闽侯区aa街道(乡镇) 餐饮区外环境 规定诱饵 最好吃餐厅 0 0 0 0 …… 0 0 0 0 20 1 有或空 xx中心 Sky John
2016/2/31 福建省福州市福州大学区bb街道(乡镇) 绿化带 规定诱饵 沧海公园 0 0 0 0 …… 0 0 0 0 20 1 有或空 xx中心 Sky John
2016/3/2 福建省福州市鼓楼区uu街道(乡镇) 农贸市场 规定诱饵 第一市场 0 0 0 0 …… 0 0 0 0 20 1 有或空 xx中心 Sky John
…… ……
年-月-日 xx省yy市zz区aa街道(乡镇) 餐饮区外环境 规定诱饵 最好吃餐厅 0 0 0 0 …… 0 0 0 0 20 1 有或空 xx中心 Sky John

对Input和Output的综合分析

结合Output推测从Input中提取的信息

第一行是表头(无实际意义)
第二行是调查日期,其实可以使用正则表达式提取年份和月份,但我直接从文件名种提取了,所以这一行对我也没有用。
第三行是调查地点,在这行需要提取省(自治区、直辖市),地(市)和县(区)
第四行需要提取气温,风力和天气
第五行需要提取诱饵种类
第7行-第8行的每一列都需要提取,不论有信息还是NaN
第9行-倒数第2行为空,需要跳过
最后一行需要提取填报单位,填报人和审核人

注意

  1. 使用正则表达式提取别人整理的excel数据肯定会出现各种bug,数据极有可能不像表面上一样规范,所以需要尽可能观察并兼容多的情况
  2. 在本表格中,提取出来的日期其实是小数(1.29),需要转换
  3. 本表格中有的NaN需要被处理为0,有的不需要处理
  4. 在Output表的列顺序与原表不同且有扩充,需要创建新列

函数功能分析

get_file_name(base_path)

比第一次实战总结做出了改进,使用了os.path.join和file.endswith这两个函数

def get_file_name(base_path): # 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

get_year_month(file)

从文件名中提取年和月,file是文件名(叫file_name更好)

def get_year_month(file): 
    ym = re.findall(r'([0-9]{4}\.?[0-9]{2})', file) #文件名是包含yyyy.mm或yyyymm的格式
    if ym[0][4] == '.':  # 去掉小数点
        ym[0] = ym[0][:4] + ym[0][5:]
    ym[0] = ym[0][:4] + '-' + ym[0][4:] #将格式转为yyyy-mm
    return ym[0] 

set_df_date(value, ym):

value是Input表格中日期列的值,是小数;ym是get_year_month(file)提取的年月,格式为yyyy-mm

def set_df_date(value, ym):
    string = str(value) # 将小数强转为str
    date = string[string.find('.') + 1:]  # 找到日
    date = ym + '-' + date  # 将年月和日结合形成时间:yyyy-mm-dd
    return date

get_sheet_df(file_name, sheet_name, col_names)

file_name为传入的文件名,sheet_name为sheet名,col_names是行数,即从第几行开始是表头(所有列的列名)

def get_sheet_df(file_name, sheet_name, col_names):
    work_book = xlrd.open_workbook(file_name) # 打开excel文件
    sheet = work_book.sheet_by_name(sheet_name) # 根据sheet_name找sheet
    # print(sheet.row_values(col_names)[0])

    df = pd.DataFrame([], columns=sheet.row_values(col_names)) # 根据列名建一个空的DataFrame
    for i in range(col_names + 1, sheet.nrows):
        if sheet.cell_value(i, 1) is '':  # excel中的空单元格数据为空字符串,也就是''
            break
        df.loc[i] = sheet.row_values(i)

    df.drop(['合计', '蝇种列合计', '布笼数'], axis=1, inplace=True)  # 丢掉没用的列,axis=1代表[row,col]的col
    df.replace('', 0, inplace=True)
    df.reset_index(drop=True, inplace=True)  # 重设索引,drop=True表示丢掉原本的索引
    # print(df)

    # 使用不同的函数得到每一列的准确数据
    invs_place = set_invs_place(sheet)
    temp, wind, weather = get_temp_wind_weather(sheet) # temp为temperature缩写
    bait_type = get_bait_type(sheet)
    unit, reporter, people_in_charge = get_unit_Reporter_PeopleInCharge(sheet)
    date = set_df_date(df.loc[0, '日期'], get_year_month(file_name))  # df.loc[0,'日期']因为同一个表格同一天的日期都是一样的,所以可以直接取一个点

    # 赋值与调整值
    for i in range(df.shape[0]):
        df.loc[i, '乡镇(街道)'] = invs_place + df.loc[i, '乡镇(街道)'] + '乡镇(街道)'
        df.loc[i, '气温(℃)*'] = temp
        df.loc[i, '风力(级)*'] = wind
        df.loc[i, '天气*'] = weather
        df.loc[i, '诱饵*'] = bait_type
        df.loc[i, '监测单位*'] = unit
        df.loc[i, '监测人*'] = reporter
        df.loc[i, '审核人'] = people_in_charge
        df.loc[i, '日期'] = date
        if df.loc[i, '环境类型'] == '餐饮外环境':
            df.loc[i, '环境类型'] = '餐饮区外环境'


    df.rename(columns={
     '序号': '笼编号', '日期': '调查日期*', '乡镇(街道)': '调查地点*'}, inplace=True) # 重命名列名

    # 补充新空列
    df[['经度', '纬度', '棕尾别麻蝇*', '瘦叶带绿蝇*']] = df.apply(lambda x: ('', '', '', ''), axis=1, result_type='expand')

    # 调整列的位置
    df = adjust_columns_position(df)
    return df

adjust_columns_position(df)

因为列的顺序有变换,所以这里手动调整位置(不懂有没有更好的方法)
补充:DataFrame调整列顺序有两个常见方法:

  1. 直接传入完整列名listdf.columns = ['aa','bb','cc']
  2. 如下方法:先保存原本的列,然后删除,再手动插入到df指定位置中
    总结:两种方法都很麻烦,目前不知道有没有更好的办法。
def adjust_columns_position(df):
    df_time, df_no, df_bz, df_bait, df_jd, df_wd, df_sy, df_zy = \
        df['调查日期*'], df['笼编号'], df['备注'], df['诱饵*'], df['经度'], df['纬度'], df['瘦叶带绿蝇*'], df['棕尾别麻蝇*']
    df.drop(['调查日期*', '笼编号', '备注', '诱饵*', '经度', '纬度', '棕尾别麻蝇*', '瘦叶带绿蝇*'], axis=1, inplace=True)
    df.insert(0, '调查日期*', df_time)
    df.insert(3, '诱饵*', df_bait)
    df.insert(18, '棕尾别麻蝇*', df_zy)
    df.insert(20, '瘦叶带绿蝇*', df_sy)
    df.insert(22, '经度', df_jd)
    df.insert(23, '纬度', df_wd)
    df.insert(27, '笼编号', df_no)
    df.insert(31, '备注', df_bz)
    return df

set_invs_place(sheet)

第三行是调查地点,本来是要根据正则表达式提取结果,但是有的表格没有填写省市,为了方便起见遂直接定义省市,且已知区县,其实这边更好的处理是如果有就返回正确值,没有就返回空,之后会进行改进。
正则表达式零宽断言的用法:
(?<=expression):表示匹配expression之后的值
(?=expression):表示匹配expression之前的值
这边结合两者,就可以直接提取值了
[\u4e00-\u9fa5]:只匹配汉字
\s:匹配空格,tab等
之后的几个函数都有用到这边列举的思想

def set_invs_place(sheet):
    total = re.findall(r'(?<=\s)[\u4e00-\u9fa5]+(?=\s)', sheet.row_values(2)[0])  # 第三行是调查地点
    base_place = '福建省福州市'
    for value in total:
        if value == '闽侯' or value == '鼓楼':
            return base_place + value + '区'

get_temp_wind_weather(sheet)

\d:匹配数字
.:匹配小数点,注意和.区分,没有转义的小数点什么都能匹配

def get_temp_wind_weather(sheet):
    temp_wind = re.findall(r'(?<=[\s:])[\d\.]+(?=[\s℃])', sheet.row_values(3)[0])  # 第四行是气温,风力,天气
    weather = re.findall(r'(?<=[(天气:)\s])[\u4e00-\u9fa5]+(?=[√☑])', sheet.row_values(3)[0])
    # print(temp_wind)
    # print(weather)
    return temp_wind[0], temp_wind[1], weather

get_bait_type(sheet)

def get_bait_type(sheet):
    bait_type = re.findall(r'(?<=诱饵种类:)[\u4e00-\u9fa5]+(?=[√□☑])', sheet.row_values(4)[0])  # 第五行是诱饵种类
    # print(bait_type)
    return bait_type

get_unit_Reporter_PeopleInCharge(sheet)

def get_unit_Reporter_PeopleInCharge(sheet):
    total = re.findall(r'(?<=\s)[\u4e00-\u9fa5a-z]+(?=\s)', sheet.row_values(56)[0])  # 第五十七行是填报单位,填报人,审核人
    # [\u4e00-\u9fa5]代表只匹配汉字
    # print(total)
    if len(total) == 3: ## 有的没有审核人,所以len可能是2或3
        return total[0], total[1], total[2]
    elif len(total) == 2:
        return total[0], total[1], None

save_file(df, path, region)

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

完整代码

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


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_year_month(file):
    ym = re.findall(r'([0-9]{4}\.?[0-9]{2})', file)
    if ym[0][4] == '.':  # 去掉.
        ym[0] = ym[0][:4] + ym[0][5:]
    ym[0] = ym[0][:4] + '-' + ym[0][4:]
    # for v in ym_total:
    #     print(v)
    return ym[0]


def set_df_date(value, ym):
    string = str(value)
    date = string[string.find('.') + 1:]  # 找到日
    date = ym + '-' + date  # 将年月和日结合形成时间
    return date


def get_sheet_df(file_name, sheet_name, col_names):
    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=sheet.row_values(col_names))
    for i in range(col_names + 1, sheet.nrows):
        if sheet.cell_value(i, 1) is '':  # excel中的空单元格数据为空字符串,也就是''
            break
        df.loc[i] = sheet.row_values(i)

    df.drop(['合计', '蝇种列合计', '布笼数'], axis=1, inplace=True)  # 丢掉没用的列,axis=1代表[row,col]的col
    df.replace('', 0, inplace=True)
    df.reset_index(drop=True, inplace=True)  # 重设索引,drop=True表示丢掉原本的索引
    # print(df)

    # 得到每一列的准确数据
    invs_place = set_invs_place(sheet)
    temp, wind, weather = get_temp_wind_weather(sheet)
    bait_type = get_bait_type(sheet)
    unit, reporter, people_in_charge = get_unit_Reporter_PeopleInCharge(sheet)
    date = set_df_date(df.loc[0, '日期'], get_year_month(file_name))  # df.loc[0,'日期']因为同一个表格同一天的日期都是一样的,所以可以直接取一个点

    # 赋值与调整值
    for i in range(df.shape[0]):
        df.loc[i, '乡镇(街道)'] = invs_place + df.loc[i, '乡镇(街道)'] + '乡镇(街道)'
        df.loc[i, '气温(℃)*'] = temp
        df.loc[i, '风力(级)*'] = wind
        df.loc[i, '天气*'] = weather
        df.loc[i, '诱饵*'] = bait_type
        df.loc[i, '监测单位*'] = unit
        df.loc[i, '监测人*'] = reporter
        df.loc[i, '审核人'] = people_in_charge
        df.loc[i, '日期'] = date
        if df.loc[i, '环境类型'] == '餐饮外环境':
            df.loc[i, '环境类型'] = '餐饮区外环境'


    df.rename(columns={
     '序号': '笼编号', '日期': '调查日期*', '乡镇(街道)': '调查地点*'}, inplace=True)

    # 补充新空列
    df[['经度', '纬度', '棕蝇*', '绿蝇*']] = df.apply(lambda x: ('', '', '', ''), axis=1, result_type='expand')

    # 调整列的位置
    df = adjust_columns_position(df)
    return df


def adjust_columns_position(df):
    df_time, df_no, df_bz, df_bait, df_jd, df_wd, df_sy, df_zy = \
        df['调查日期*'], df['笼编号'], df['备注'], df['诱饵*'], df['经度'], df['纬度'], df['绿蝇*'], df['棕蝇*']
    df.drop(['调查日期*', '笼编号', '备注', '诱饵*', '经度', '纬度', '棕蝇*', '绿蝇*'], axis=1, inplace=True)
    df.insert(0, '调查日期*', df_time)
    df.insert(3, '诱饵*', df_bait)
    df.insert(18, '棕蝇*', df_zy)
    df.insert(20, '绿蝇*', df_sy)
    df.insert(22, '经度', df_jd)
    df.insert(23, '纬度', df_wd)
    df.insert(27, '笼编号', df_no)
    df.insert(31, '备注', df_bz)
    return df

def set_invs_place(sheet):
    total = re.findall(r'(?<=\s)[\u4e00-\u9fa5]+(?=\s)', sheet.row_values(2)[0])  # 第三行是调查地点
    base_place = '福建省福州市'
    for value in total:
        if value == '鼓楼' or value == '闽侯':
            return base_place + value + '区'


def get_temp_wind_weather(sheet):
    temp_wind = re.findall(r'(?<=[\s:])[\d\.]+(?=[\s℃])', sheet.row_values(3)[0])  # 第四行是气温,风力,天气
    weather = re.findall(r'(?<=[(天气:)\s])[\u4e00-\u9fa5]+(?=[√☑])', sheet.row_values(3)[0])
    # print(temp_wind)
    # print(weather)
    return temp_wind[0], temp_wind[1], weather


def get_bait_type(sheet):
    bait_type = re.findall(r'(?<=诱饵种类:)[\u4e00-\u9fa5]+(?=[√□☑])', sheet.row_values(4)[0])  # 第五行是诱饵种类
    # print(bait_type)
    return bait_type


def get_unit_Reporter_PeopleInCharge(sheet):
    total = re.findall(r'(?<=\s)[\u4e00-\u9fa5a-z]+(?=\s)', sheet.row_values(56)[0])  # 第五十七行是填报单位,填报人,审核人
    # [\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


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


if __name__ == '__main__':
    base_path = r'E:\蝇密度汇总\2018-2020蝇密度'
    file_collection = get_file_name(base_path)
    df = [pd.DataFrame([]) for _ in range(2)]
    for i, file in enumerate(file_collection):
        print(i, ' ', file)
    for i, file in enumerate(file_collection):
        # print(file)
        if i < 12 or i >= 24 and i < 33:
            df[0] = df[0].append(get_sheet_df(file_name=file, sheet_name='县区1', col_names=5))
        else:
            df[1] = df[1].append(get_sheet_df(file_name=file, sheet_name='县区2', col_names=5))

    print(df[0])
    print(df[1])
    regions = ['鼓楼区', '闽侯区']
    for i in range(2):
        save_file(df=df[i], path=base_path, region=regions[i])

你可能感兴趣的:(实习生涯,比赛/练习,excel,正则表达式,pandas)