From 9930e6fee14a92513eda30e3de443c563eaf173d Mon Sep 17 00:00:00 2001 From: marslbr Date: Mon, 10 Nov 2025 18:59:16 +0800 Subject: [PATCH] =?UTF-8?q?251110=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KANO/KANO模型客户调研问卷.xlsx | Bin 0 -> 10179 bytes KANO/main.py | 498 +++++++++++++++++++++++++++++++++ 2 files changed, 498 insertions(+) create mode 100644 KANO/KANO模型客户调研问卷.xlsx create mode 100644 KANO/main.py diff --git a/KANO/KANO模型客户调研问卷.xlsx b/KANO/KANO模型客户调研问卷.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c2b72aed25deaa36cd8f51385bb9ebb452a9bf1d GIT binary patch literal 10179 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 literal 0 HcmV?d00001 diff --git a/KANO/main.py b/KANO/main.py new file mode 100644 index 0000000..2c294f0 --- /dev/null +++ b/KANO/main.py @@ -0,0 +1,498 @@ +# -*- coding: utf-8 -*- + +""" +脚本说明: +本脚本用于KANO需求分析,能够处理问卷数据并输出需求分类结果 +""" + +import pandas + +import prettytable + + +print("1 打开并读取Excel文件...", end="") + +try: + dataset = pandas.read_excel(io="KANO模型客户调研问卷.xlsx", sheet_name="问卷结果") + + # 选项范围 + alternatives = ["非常满意", "理应如此", "无所谓", "勉强接受", "不满意"] + + # 数据清洗 + dataset = ( + dataset.iloc[ + :, 3: + ] # 原始数据第一列为编号、第二列为提交人、第三列为提交时间,从第四列到最后一列为选项,删除第一列至第三列 + .map( + lambda cell: cell if cell in alternatives else pandas.NA + ) # 检查是否在选项范围,若不在选项范围则置为缺失值 + .dropna(axis="index", how="any") # 删除缺失值 + .loc[lambda dataframe: dataframe.nunique(axis=1) != 1] # 删除相同选项的样本 + ) + + # 统计样本数 + samples_size = dataset.shape[0] + # 若样本数为0则抛出异常 + if samples_size == 0: + raise Exception("样本数为0") + + # 统计列数 + columns_counts = dataset.shape[1] + # 若列数非奇数则抛出异常 + if columns_counts % 2 != 0: + raise Exception("列数为奇数") + + print(f"已完成,样本数为{samples_size}") + +except Exception as exception: + print(f"发生异常:{str(exception)}") + exit() + +# 读取问卷题目和答案(从第7列开始为题目或答案) +DataSet = DataSet.iloc[:, 6:] + +# 统计数据集中样本数量和题目数量 +Sample_Size, Question_Amount = DataSet.shape + +# 判断题目数量是否为偶数,若为偶数则计算问卷中涉及需求数量,若为奇数则终止脚本 +if Question_Amount % 2 == 0: + + # 计算问卷中涉及需求数量 + Requirement_Amount = int(Question_Amount / 2) + +else: + + print("题目数量为奇数,请检查") + print("") + + exit() + +print( + "数据集中包含 %d 份样本, %d 个问题(涉及 %d 个需求)" + % (Sample_Size, Question_Amount, Requirement_Amount) +) +print("") + +print("*" * 100) +print("") + +print("2 数据预处理") +print("") + +print("2.1 检查并删除不规范样本") +print("") + +# 定义问卷中备选答案列表 +Alternatives = ["我很喜欢", "理所应当", "无所谓", "勉强接受", "我很不喜欢"] + +# 检查答案是否在指定范围,若否则将该答案定义为空值 +DataSet = DataSet.applymap(lambda x: x if x in Alternatives else None) + +# 删除包含缺失值的样本 +DataSet.dropna(axis="index", how="any", inplace=True) + +# 删除答案全部相同的样本 +DataSet = DataSet[DataSet.apply(pandas.Series.nunique, axis="columns") != 1] + +# 统计有效样本数量 +Sample_Size = DataSet.shape[0] + +print("处理后,有效样本数量为 %d 份" % (Sample_Size)) +print("") + +print("*" * 100) +print("") + +print("3 数据处理") +print("") + +Requirement_Labels = DataSet.columns.tolist() + +# 通过问题截取需求名称(截取'有'右侧、','左侧字符串) +Requirement_Labels = [ + x[x.find("有") + 1 : x.find(",")] + for x in Requirement_Labels + if isinstance(x, str) and "具有" in x +] + +print("3.1 绘制KANO评价结果分类对照表") +print("") + +for Question_Number in range(Requirement_Amount): + + # 创建KANO评价结果分类对照表 + KANO = pandas.DataFrame(data=[], index=Alternatives, columns=Alternatives) + + for Column_Label in Alternatives: + + for Index_Label in Alternatives: + + # 统计并赋值 + KANO.loc[Index_Label, Column_Label] = DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin([Index_Label])) + & (DataSet.iloc[:, Question_Number + 1].isin([Column_Label])), + :, + ].shape[0] + + # 修改行名 + KANO.index = [ + "Provide_Like", + "Provide_Should", + "Provide_Indifferent", + "Provide_Grudging", + "Provide_Hate", + ] + + # 重置索引 + KANO.reset_index(inplace=True) + + # 修改列名 + KANO.columns = [ + "", + "Not_Provide_Like", + "Not_Provide_Should", + "Not_Provide_Indifferent", + "Not_Provide_Grudging", + "Not_Provide_Hate", + ] + + # 打印表格 + + PrintTable = prettytable.PrettyTable() + + PrintTable.field_names = KANO.columns.tolist() + + for Index in KANO.index.tolist(): + + PrintTable.add_row(KANO.loc[Index]) + + PrintTable.align = "r" + + PrintTable.align[""] = "l" + + PrintTable.float_format = "." + + print( + "附表 需求%d:%s的KANO评价结果分类对照表:" + % (Question_Number + 1, Requirement_Labels[Question_Number]) + ) + + print(PrintTable) + + print("") + +print("字段说明:") + +print( + "1)Not_Provide_Like为不提供该需求、用户表示“我很喜欢”,Not_Provide_Should为不提供该需求、用户表示“理所应当”,Not_Provide_Indifferent为不提供该需求、用户表示“无所谓”,Not_Provide_Grudging为不提供该需求、用户表示“勉强接受”,Not_Provide_Hate为不提供该需求、用户表示“我很不喜欢”。" +) + +print( + "1)Provide_Like为提供该需求、用户表示“我很喜欢”,Provide_Should为提供该需求、用户表示“理所应当”,Provide_Indifferent为提供该需求、用户表示“无所谓”,Provide_Grudging为提供该需求、用户表示“勉强接受”,Provide_Hate为不提供该需求、用户表示“我很不喜欢”。" +) + +print("") + +print("3.2 计算KANO评价维度") +print("") + +# 创建KANO各维度分数表 +KANO_Report = pandas.DataFrame( + data=[], + columns=[ + "Requirement_Label", + "A_Score", + "O_Score", + "M_Score", + "I_Score", + "R_Score", + "Q_Score", + ], + dtype="float", +) + +KANO_Report["Requirement_Label"] = Requirement_Labels + +for Question_Number in range(Requirement_Amount): + + # 计算兴奋型需求分数 + A_Score = round( + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["我很喜欢"])) + & ( + DataSet.iloc[:, Question_Number + 1].isin( + ["理所应当", "无所谓", "勉强接受"] + ) + ), + :, + ].shape[0] + / Sample_Size + * 100, + 2, + ) + + KANO_Report.loc[Question_Number, "A_Score"] = A_Score + + # 计算期望型需求分数 + O_Score = round( + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["我很喜欢"])) + & (DataSet.iloc[:, Question_Number + 1].isin(["我很不喜欢"])), + :, + ].shape[0] + / Sample_Size + * 100, + 2, + ) + + KANO_Report.loc[Question_Number, "O_Score"] = O_Score + + # 计算必备型需求分数 + M_Score = round( + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["理所应当", "无所谓", "勉强接受"])) + & (DataSet.iloc[:, Question_Number + 1].isin(["我很不喜欢"])), + :, + ].shape[0] + / Sample_Size + * 100, + 2, + ) + + KANO_Report.loc[Question_Number, "M_Score"] = M_Score + + # 计算无差型需求分数 + I_Score = round( + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["理所应当", "无所谓", "勉强接受"])) + & ( + DataSet.iloc[:, Question_Number + 1].isin( + ["理所应当", "无所谓", "勉强接受"] + ) + ), + :, + ].shape[0] + / Sample_Size + * 100, + 2, + ) + + KANO_Report.loc[Question_Number, "I_Score"] = I_Score + + # 计算反向型需求分数 + R_Score = round( + DataSet.loc[ + ( + DataSet.iloc[:, Question_Number].isin( + ["理所应当", "无所谓", "勉强接受", "我很不喜欢"] + ) + ) + & ( + DataSet.iloc[:, Question_Number + 1].isin( + ["我很喜欢", "理所应当", "无所谓", "勉强接受"] + ) + ), + :, + ].shape[0] + / Sample_Size + * 100 + - I_Score, + 2, + ) + + KANO_Report.loc[Question_Number, "R_Score"] = R_Score + + # 计算可疑型需求分数 + Q_Score = round( + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["我很喜欢"])) + & (DataSet.iloc[:, Question_Number + 1].isin(["我很喜欢"])), + :, + ].shape[0] + / Sample_Size + * 100 + + DataSet.loc[ + (DataSet.iloc[:, Question_Number].isin(["我很不喜欢"])) + & (DataSet.iloc[:, Question_Number + 1].isin(["我很不喜欢"])), + :, + ].shape[0] + / Sample_Size + * 100, + 2, + ) + + KANO_Report.loc[Question_Number, "Q_Score"] = Q_Score + +# 打印表格 + +PrintTable = prettytable.PrettyTable() + +PrintTable.field_names = KANO_Report.columns.tolist() + +for Index in KANO_Report.index.tolist(): + + PrintTable.add_row(KANO_Report.loc[Index]) + +PrintTable.align["Requirement_Label"] = "l" + +PrintTable.align["A_Score"] = "r" + +PrintTable.align["O_Score"] = "r" + +PrintTable.align["M_Score"] = "r" + +PrintTable.align["I_Score"] = "r" + +PrintTable.align["R_Score"] = "r" + +PrintTable.align["Q_Score"] = "r" + +PrintTable.align["Better_Score"] = "r" + +PrintTable.align["Worse_Score"] = "r" + +PrintTable.float_format["A_Score"] = ".2" + +PrintTable.float_format["O_Score"] = ".2" + +PrintTable.float_format["M_Score"] = ".2" + +PrintTable.float_format["I_Score"] = ".2" + +PrintTable.float_format["R_Score"] = ".2" + +PrintTable.float_format["Q_Score"] = ".2" + +PrintTable.float_format["Better_Score"] = ".2" + +PrintTable.float_format["Worse_Score"] = ".2" + +print("附表 各需求的KANO评价维度计算结果:") + +print( + PrintTable.get_string( + fields=[ + "Requirement_Label", + "A_Score", + "O_Score", + "M_Score", + "I_Score", + "R_Score", + "Q_Score", + ] + ) +) + +print("字段说明:") + +print( + "1)Requirement_Label为需求名称,A_Score为兴奋型需求分数,O_Score为期望型需求分数,M_Score为必备型需求分数,I_Score为无差型需求分数,R_Score为反向型需求分数,Q_Score为可疑型需求分数。" +) + +print("") + +print("3.3 定义需求类型和Better-Worse分数") +print("") + +# 以KANO评价维度中最高分定义需求类型 +Requirement_Types = list( + KANO_Report[ + ["A_Score", "O_Score", "M_Score", "I_Score", "R_Score", "Q_Score"] + ].idxmax(axis="columns") +) + +# 通过列名截取需求类型(第一位、'_'左侧字符串) +Requirement_Types = [ + x[0 : x.find("_")] for x in Requirement_Types if isinstance(x, str) +] + +KANO_Report["Requirement_Type"] = Requirement_Types + +# 计算Better分数 +KANO_Report["Better_Score"] = ( + (KANO_Report["A_Score"] + KANO_Report["O_Score"]) + / ( + KANO_Report["A_Score"] + + KANO_Report["O_Score"] + + KANO_Report["M_Score"] + + KANO_Report["I_Score"] + ) + * 100 +) + +# 计算Worse分数 +KANO_Report["Worse_Score"] = ( + -1 + * (KANO_Report["O_Score"] + KANO_Report["M_Score"]) + / ( + KANO_Report["A_Score"] + + KANO_Report["O_Score"] + + KANO_Report["M_Score"] + + KANO_Report["I_Score"] + ) + * 100 +) + +# 打印表格 + +PrintTable = prettytable.PrettyTable() + +PrintTable.field_names = KANO_Report.columns.tolist() + +for Index in KANO_Report.index.tolist(): + + PrintTable.add_row(KANO_Report.loc[Index]) + +PrintTable.align["Requirement_Label"] = "l" + +PrintTable.align["Requirement_Type"] = "r" + +PrintTable.align["Better_Score"] = "r" + +PrintTable.align["Worse_Score"] = "r" + +PrintTable.float_format["Better_Score"] = ".2" + +PrintTable.float_format["Worse_Score"] = ".2" + +print("附表 各需求的KANO评价维度计算结果:") + +print( + PrintTable.get_string( + fields=["Requirement_Label", "Requirement_Type", "Better_Score", "Worse_Score"] + ) +) + +print("字段说明:") + +print("1)Requirement_Label为需求名称,Requirement_Type为需求类型。") + +print( + "2)A为兴奋型需求:表示产品具有该种需求,则用户满意度会提高;没有该种需求,则用户满意度不会降低。建议给予P3关注。" +) + +print( + "3)O为期望型需求:表示产品具有该种需求,则用户满意度会提高;没有该种需求,则用户满意度会降低。建议给予P1关注。" +) + +print( + "4)M为必备型需求:表示产品具有该种需求,则用户满意度不会提高;没有该种需求,则用户满意度会降低。建议给予P2关注。" +) + +print( + "5)I为无差型需求:表示产品具有该种需求,则用户满意度不会提高;没有该种需求,则用户满意度不会降低。建议给予P4关注。" +) + +print("6)R为反向型需求:建议给予关注。") + +print("7)Q为可疑型需求:建议给予关注。") + +print( + "8)Better_Score为Better分数。表示如果产品具有某种需求,则用户满意度会提高,数值越大提高越大。" +) + +print( + "9)Worse_Score为Worse分数。表示如果产品没有某种需求,则用户满意度会下降,数值越小下降越大。" +) + +print("")