前言
陆陆续续又进行了几次数据处理,这些任务都大体沿用了第二次实战的总结过的思路和方法,效率都挺高的。今日再次有了一些新思路,便再次总结一番。
前一版本的不足
第二次实战中有所不足:
- 无法处理excel的sheet中出现的同一时间的多份不同地点的统计数据
- 需要手动设定行数
- 在字符串的正则表达式匹配上有所不足
- 无法处理excel多重表头(excel中出现合并单元格之后怎么办?MultiIndex如何处理?)
- 没有中间存储的机制(有中间数据先进行保存如果有需要手动检查的会更方便)
Input
输入的表格
文件夹中的excel文件,格式如下:
Output
其中一行的输入类似:
对Input和Output的综合分析
表格解读
第一行是表头,可以起到定位作用,因为在同一个sheet中有多个同一时间不同地点的表格(很不规范有没有,干嘛不多建几个sheet)
第二行是调查时间,包括年月日,很好
第三行是调查地点,根据Output的情况可以发现调查地点只接受到街道(乡镇)一级,检测地点是村一级与地址门牌的结合。
第四行是环境类型
第五行~第七行是多重表头,按照xlrd对多重表头的读取,合并后的单元格只有第一次出现的行列是值,其余位置皆为空字符串,例如:“编号”的位置是(5, A),而(6, A)和(7, A)都是空字符串,“植物”的位置是(6, E),而(6, F)的位置是空字符串。多重表头的处理个人认为是个小难点。
第八行~第三十二行是表格内容,根据观察发现表格内容的行数不固定,需要注意漏读的风险。
第三十三行是合计,与表头同样可以起到定位作用
第三十四行是韦恩图指数(乱编的),没什么用
第三十五行是一些注意事项,对于理解表格内容有帮助。
第三十六行是监测单位,监测人和审核人,同时也是一个表格的最后一行。
分析
-
采取定位。在同一个sheet的不同表格之间有不等的空行,为了更方便读取数据,我通过定位表头,合计和监测单位三行进行表格内容的读取(第三十五行在某些表格中不存在)。
-
在读取调查时间,调查地点,环境类型,监测单位,监测人和审核人数据时因为下划线的原因需要先对数据进行一下替换,可用str.replace函数
-
多重表头变单行表格,我通过画图来找映射关系,千万不要小看了可视化的力量。 手绘图如下:
-
多重表格使得直接读取不太方便,于是我直接手动设置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')
总结
这次任务耗时近两天,之前没有通过可视化的方式画出映射,在此上花费太多时间。在结构方面需要更加灵活,而不是死板地设置表格长度。