Python 玩数据分析:统计 Excel 并用 Matplotlib 绘图

  • 数据无处不在
  • 我希望我懂得如何使用基本的电子公式
  • 一图胜千言
  • 完整代码


 作者:高玉涵
 时间:2022.6.19 11:07 父亲节

“人皆有所不忍,达之于其所忍,仁也;人皆有所不为,达之于其所为,义也。”——孟子曰

数据无处不在

 近日为验证新环境能否适配现有业务,临时从各组抽调 16 位老师分 5 个小组来帮助进行业务测试。为了能及时掌握测试工作整体、小组和个人进度,每日测试终了要求各位老师,将各自测试通过的交易和案例数进行上报汇总。

 本着不重复造轮子的指导思想。数据填报收集的工作。一开始,我是想自己写个简单的小程序。但,转念想“麻雀虽小”要做的事情可一样也少不了。例如,界面设计、代码编写、输入有效性检查、数据库、托管平台等等。原本计划也只是利用 2 周时间来完成测试工作,这样下来投入与产出比不高啊(说人话就是:划不来)。遂,在网上查找是否有现成替代方案,一找之下金山表单跃入眼帘,在这里我要吹爆它(以前我对金山的认识仅限于 WPS) 其丰富的模板和高度可定制化的功能,最最重要的是还免费。这些正是我需要的,摆在我眼前的第一个难题迎刃而解。下图就是我定制的表单界面。

python統計圖 csv python统计图表excel matplotlib_python統計圖 csv

我希望我懂得如何使用基本的电子公式

 解决了数据收集的问题,只能说解决了问题的一半。金山表单会将收集到的数据,以一张很大很复杂的 Excel 表格展现出来(这与你定制的表单、数据规模而异)你能从下图表格中一眼分析出你想要的结果数据吗?

python統計圖 csv python统计图表excel matplotlib_Matplotlib_02

 对于各位 Excel 高手来说(可能正是观看此文的你)有了这张表再利用 Excel 函数把相关数据进行分析,就足以得到想要的结果。不幸的是,我对 Excel 的认知仅限设置单元格四条边线是实还是虚。

一图胜千言

 大脑追逐图像,而非文字。在大脑的活动中,一第图片胜过千言万语。为了能说明问题并给阅读者营造丰富的感觉,我决定以图表的方式展现分析结果,下图就是生成的结果,是不是比枯燥的文字更让你感兴趣。

python統計圖 csv python统计图表excel matplotlib_python_03


python統計圖 csv python统计图表excel matplotlib_Excel_04


python統計圖 csv python统计图表excel matplotlib_Excel_05


python統計圖 csv python统计图表excel matplotlib_python_06

完整代码

 这里给出了分析 Excel 数据与绘制图表的完整代码,你可以把它用在你的程序或文档中。除非你使用的表格和我完全一样,否则需根据你的 Excel 数据做相应的修改。对于这种只写一次的程序,我并没有去优化算法,程序中存在冗余代码和硬编码,细心的你可能还发现缺少必要的异常处理。这些在我的应用里都不是问题,但如果是使用的你可能要特别小心了。

'''
    功能:分析表格数据按需绘制图表
    作者:高玉涵
    时间:2022.6.19
'''
import numpy as np
import openpyxl
import matplotlib.pyplot as plt

def open_excel(fileName, sheetName):
    work = openpyxl.load_workbook(fileName)
    sheet = work.get_sheet_by_name(sheetName)
    return sheet

def person(sheet, thead):
    '''
        按人统计
    '''
    record = {}
    for row in range(2, sheet.max_row + 1):
        '''
            {} 初始化部分
        '''
        name = sheet.cell(row=row, column=thead['info']).value
        if name not in record:
            record.setdefault(name, {})

        rq = sheet.cell(row=row, column=thead['rq']).value
        rq = rq.strftime('%Y-%m-%d')
        if rq not in record[name]:
            record[name].setdefault(rq, {})
            record[name][rq].setdefault('al', 0)    # 案例数
            record[name][rq].setdefault('jym', 0)   # 交易数

        for col in thead['jym']:
            jym = sheet.cell(row=row, column=col).value
            # 假定 每列只登记一个交易码
            if jym != "" and jym != None and jym.find('无') == -1:
                record[name][rq]['jym'] += 1
        for col in thead['al']:
            al = sheet.cell(row=row, column=col).value
            if al != "" and al != None:
                record[name][rq]['al'] += int(al)
    return record

def thead_position(sheet):
    '''
        定位标题行 交易码,案例数,填报人等 在表格中的位置
    '''
    thead = {'jym':[], 'al':[], 'info':0, 'mk':0, 'rq':''}
    for col in range(1, sheet.max_column + 1):
        value = sheet.cell(row=1, column=col).value
        if value.find("交易名称") >=0:
            thead['jym'].append(col)
        elif value.find("案例数") >=0:
            thead['al'].append(col)
        elif value.find("日期") >=0:
            thead['rq'] = col
        elif value.find("模块") >=0:
            thead['mk'] = col
        elif value.find("填报人") >=0:
            thead['info'] = col
    return thead

def graph_line(record):
    '''
        绘折线图
    '''
    dateCount = {}  # 按日计数
    for name in record.keys():
        for rq in record[name].keys():
            if rq not in dateCount:
                dateCount.setdefault(rq, {'jym':0, 'al':0})
            dateCount[rq]['jym'] += record[name][rq]['jym']
            dateCount[rq]['al'] += record[name][rq]['al']
    labels = [rq for rq in dateCount.keys()]
    line_jym = []
    line_al = []
    sum_jym = 0
    sum_al = 0
    for rq in labels:
        line_jym.append(dateCount[rq]['jym'])
        line_al.append(dateCount[rq]['al'])
        sum_jym += dateCount[rq]['jym']
        sum_al += dateCount[rq]['al']

    bar_width = 0.35
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.title("X86 测试每日完成情况(单位:个)")
    plt.xlabel(f"日期    数据截止:2022.6.18  交易:{sum_jym}  案例:{sum_al}")
    plt.ylabel("数量")
    plt.box(False)

    line1, = plt.plot(labels,line_jym, color='r', label='交易')
    line2, = plt.plot(labels, line_al, color='b', label='案例')
    plt.legend(handles=[line1, line2], labels=['交易','案例'], loc='best')

    for i, j in enumerate(line_jym):
        plt.text(i-0.5*bar_width-0.05, j+0.1, str(j))
    for i, j in enumerate(line_al):
        plt.text(i+0.5*bar_width-0.05, j+0.1, str(j))
    plt.show()

def mk(sheet, thead):
    '''
        按模块统计
    '''
    record = {}
    for row in range(2, sheet.max_row + 1):
        '''
            {} 初始化部分
        '''
        mk = sheet.cell(row=row, column=thead['mk']).value
        if mk not in record:
            record.setdefault(mk, {'jym':0, 'al': 0})

        for col in thead['jym']:
            jym = sheet.cell(row=row, column=col).value
            # 假定 每列只登记一个交易码
            if jym != "" and jym != None and jym.find('无') == -1:
                record[mk]['jym'] += 1
        # 统计案例数
        for col in thead['al']:
            al = sheet.cell(row=row, column=col).value
            if al != "" and al != None:
                record[mk]['al'] += int(al)
    return record

def graph_group(record):
    '''
        按组统计
    '''
    one = ['赵', '张', '闻', '沈']
    two = ['花', '周', '韩', '李', '赵']
    three = []
    four = ['陈', '娄', '陈', '杨']
    five = ['李', '郑', '张']
    group_count = {
        'one': {'jym':0, 'al':0},
        'two':{'jym':0, 'al':0},
        'three':{'jym':0, 'al':0},
        'four':{'jym':0, 'al':0},
        'five':{'jym':0, 'al':0}
    }

    for name in record.keys():
        for rq in record[name].keys():
            if name in one:
                group_count['one']['jym'] += record[name][rq]['jym']
                group_count['one']['al'] += record[name][rq]['al']
            elif name in two:
                group_count['two']['jym'] += record[name][rq]['jym']
                group_count['two']['al'] += record[name][rq]['al']
            elif name in three:
                group_count['three']['jym'] += record[name][rq]['jym']
                group_count['three']['al'] += record[name][rq]['al']
            elif name in four:
                group_count['four']['jym'] += record[name][rq]['jym']
                group_count['four']['al'] += record[name][rq]['al']
            elif name in five:
                group_count['five']['jym'] += record[name][rq]['jym']
                group_count['five']['al'] += record[name][rq]['al']

    labels = ['第一组', '第二组', '第三组', '第四组', '第五组']
    bar1 = []
    bar2 = []
    sum_jym = 0
    sum_al = 0
    for g in group_count.keys():
        bar1.append( group_count[g]['jym'] )
        bar2.append( group_count[g]['al'] )
        sum_jym += group_count[g]['jym']
        sum_al += group_count[g]['al']

    bar_width = 0.35
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.bar(np.arange(len(bar1))-0.5*bar_width, bar1, label='交易数',
        width=bar_width, color='#58c9b9')
    plt.bar(np.arange(len(bar2))+0.5*bar_width, bar2, label='案例数',
        width=bar_width, color='#519d9e')
    plt.xlabel(f"填报人    数据截止:2022.6.18  交易:{sum_jym}  案例:{sum_al}")
    plt.ylabel("数量")
    plt.title("X86 测试各组完成情况(单位:个)")
    plt.ylim([1,600])
    plt.legend()
    plt.xticks(np.arange(len(labels)), labels, fontsize=13)
    plt.box(False)
    plt.grid(color='0.4', axis='y', linestyle='solid', alpha=0.1)

    for i, j in enumerate(bar1):
        plt.text(i-0.5*bar_width-0.05, j+0.1, str(j))
    for i, j in enumerate(bar2):
        plt.text(i+0.5*bar_width-0.05, j+0.1, str(j))
    plt.show()


def graph_pie(sheet, thead):
    '''
        绘制饼图
    '''
    record = mk(sheet, thead)
    labels = [m for m in record.keys()]
    pie_jym = []
    pie_al = []
    for m in labels:
        pie_jym.append(record[m]['jym'])
        pie_al.append(record[m]['al'])

    plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文
    plt.rcParams['axes.unicode_minus'] = False      # 用来正常显负号
    plt.pie(pie_jym, labels=labels, autopct='%1.2f%%')
    plt.title('X86 完成的测试交易各模块占比')
    plt.show()

def graph_bar(record):
    '''
        绘柱壮图
    '''
    labels = [name for name in record.keys()]
    bar_width = 0.35
    bar1 = []
    bar2 = []
    sum_jym = 0
    sum_al = 0
    for n in labels:
        jym = 0
        al = 0
        # 统计
        for rq in record[n].keys():
            jym += record[n][rq]['jym']
            al += record[n][rq]['al']
        bar1.append(jym)
        bar2.append(al)
        sum_jym += jym
        sum_al += al

    plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文
    plt.rcParams['axes.unicode_minus'] = False      # 用来正常显负号
    plt.bar(np.arange(len(bar1))-0.5*bar_width, bar1, label='交易数',
        width=bar_width, color='#58c9b9')
    plt.bar(np.arange(len(bar2))+0.5*bar_width, bar2, label='案例数',
        width=bar_width, color='#519d9e')
    plt.xlabel(f"填报人    数据截止:2022.6.18  交易:{sum_jym}  案例:{sum_al}")
    plt.ylabel("数量")
    plt.title("X86 测试个人完成情况(单位:个)")
    plt.ylim([1,200])
    plt.legend()
    plt.xticks(np.arange(len(labels)), labels, fontsize=13)
    plt.box(False)
    plt.grid(color='0.4', axis='y', linestyle='solid', alpha=0.1)

    for i, j in enumerate(bar1):
        plt.text(i-0.5*bar_width-0.05, j+0.1, str(j))
    for i, j in enumerate(bar2):
        plt.text(i+0.5*bar_width-0.05, j+0.1, str(j))
    plt.show()

if __name__ == '__main__':
    sheet = open_excel('xxx.xlsx', '收集表')
    # 定位标题位置
    thead = thead_position(sheet)
    # 按人统计
    record = person(sheet, thead)
    # 绘柱壮图
    graph_bar(record)
    graph_group(record)
    # 绘折线图
    graph_line(record)
    # 绘饼图
    graph_pie(sheet, thead)