impellerDiameterV1.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import pandas as pd
  2. import re
  3. import math
  4. def extract_diameter_and_power(model_str):
  5. """
  6. 从风机机型字符串中提取叶轮直径(单位:米)和额定功率(单位:kW)。
  7. 返回一个字典:{'diameter': 直径, 'power_kw': 功率}
  8. 如果无法确定则对应值为None。
  9. """
  10. if not isinstance(model_str, str):
  11. return {'diameter': None, 'power_kw': None}
  12. s = model_str.strip().upper()
  13. # ----- 第一步:找出所有可能的数字 -----
  14. all_numbers = []
  15. matches = re.findall(r'\d+\.?\d*', s)
  16. for num_str in matches:
  17. try:
  18. num = float(num_str)
  19. all_numbers.append(num)
  20. except ValueError:
  21. continue
  22. if len(all_numbers) < 2:
  23. # 如果没有至少两个数字,无法区分直径和功率
  24. return {'diameter': None, 'power_kw': None}
  25. # ----- 第二步:根据特征区分直径和功率 -----
  26. diameter_candidates = []
  27. power_candidates = []
  28. for num in all_numbers:
  29. # 直径的特征:通常为2-3位整数,范围在50-300米之间
  30. if 20 <= num <= 400 and num > 10: # 放宽下限到20,确保包含小直径机型
  31. # 直径通常接近整数,且数值相对较小
  32. if abs(num - round(num)) < 0.1: # 接近整数
  33. diameter_candidates.append(num)
  34. elif 50 <= num <= 300: # 在典型直径范围内的小数也可能是直径
  35. diameter_candidates.append(num)
  36. # 功率的特征:
  37. # 1. 兆瓦级的小数 (如1.5, 2.0, 3.6, 6.7)
  38. # 2. 百位或千位整数 (如1500, 2000, 3000, 5000)
  39. # 3. 万位整数 (如10000, 12000)
  40. # 判断是否为兆瓦级功率(常见的小数功率)
  41. if 0.5 <= num <= 20 and '.' in str(num):
  42. power_candidates.append(num * 1000) # 转换为kW
  43. # 判断是否为千瓦级功率
  44. elif num >= 100: # 功率通常至少100kW以上
  45. # 典型功率值范围
  46. if 100 <= num <= 30000:
  47. power_candidates.append(num)
  48. # ----- 第三步:特殊处理MW单位标识 -----
  49. # 如果字符串中包含"MW"标识,可以更准确地提取功率
  50. if 'MW' in s:
  51. # 寻找靠近"MW"的数字
  52. mw_pattern = r'(\d+\.?\d*)\s*MW'
  53. mw_matches = re.findall(mw_pattern, s)
  54. for mw_str in mw_matches:
  55. try:
  56. mw_value = float(mw_str)
  57. # 转换为kW
  58. kw_value = mw_value * 1000
  59. if kw_value not in power_candidates:
  60. power_candidates.append(kw_value)
  61. except ValueError:
  62. pass
  63. # ----- 第四步:决策逻辑 -----
  64. result = {'diameter': None, 'power_kw': None}
  65. # 1. 直径决策
  66. if diameter_candidates:
  67. # 优先选择在典型直径范围(50-200)内的整数
  68. typical_diameters = [d for d in diameter_candidates if 50 <= d <= 200]
  69. if typical_diameters:
  70. # 选择第一个(通常字符串中先出现的是直径)
  71. result['diameter'] = typical_diameters[0]
  72. else:
  73. # 如果不在典型范围,选择最小的(假设直径通常比功率数值小)
  74. result['diameter'] = min(diameter_candidates)
  75. # 2. 功率决策
  76. if power_candidates:
  77. # 优先选择通过MW标识找到的功率
  78. mw_based_power = [p for p in power_candidates if p in [num * 1000 for num in all_numbers if '.' in str(num)]]
  79. if mw_based_power:
  80. result['power_kw'] = mw_based_power[0]
  81. else:
  82. # 否则选择最大的(假设功率数值通常比直径大)
  83. # 但需要排除明显是直径的值
  84. filtered_power = [p for p in power_candidates if p != result['diameter']]
  85. if filtered_power:
  86. # 对于功率,如果是整数,优先选择常见的功率等级
  87. common_powers = [1500, 2000, 2500, 3000, 5000, 6000, 10000, 12000]
  88. for cp in common_powers:
  89. if cp in [int(p) for p in filtered_power if abs(p - round(p)) < 0.1]:
  90. result['power_kw'] = cp
  91. break
  92. if result['power_kw'] is None:
  93. result['power_kw'] = max(filtered_power)
  94. # ----- 第五步:如果决策失败,尝试基于位置的简单逻辑 -----
  95. if result['diameter'] is None or result['power_kw'] is None:
  96. if len(all_numbers) >= 2:
  97. # 假设第一个数字是直径,第二个是功率(常见格式:直径-功率)
  98. if 20 <= all_numbers[0] <= 300:
  99. result['diameter'] = all_numbers[0]
  100. # 判断第二个数字是否为功率
  101. if len(all_numbers) > 1:
  102. second_num = all_numbers[1]
  103. # 如果是小数,很可能是兆瓦级功率
  104. if '.' in str(second_num) and 0.5 <= second_num <= 20:
  105. result['power_kw'] = second_num * 1000
  106. elif second_num >= 100 and second_num <= 30000:
  107. result['power_kw'] = second_num
  108. return result
  109. def calculate_swept_area(diameter):
  110. """
  111. 计算扫风面积
  112. 公式:扫风面积 = π × (叶轮直径/2)²
  113. 单位:平方米(㎡)
  114. """
  115. if diameter is None or pd.isna(diameter):
  116. return None
  117. try:
  118. # 使用高精度的π值
  119. radius = diameter / 2.0
  120. swept_area = math.pi * (radius ** 2)
  121. return round(swept_area, 2) # 保留两位小数
  122. except (TypeError, ValueError):
  123. return None
  124. def main():
  125. # ---------- 配置区:请根据您的实际文件修改 ----------
  126. input_file = f"./data/全部机型功率曲线_含标准类型.csv" # 输入文件名,支持 .csv, .xlsx, .xls
  127. output_file = f"./output/全部机型功率曲线_含标准类型_解析结果.csv" # 输出文件名
  128. model_column_name = "标准机型" # 包含机型信息的列名
  129. # -------------------------------------------------
  130. # 读取文件
  131. if input_file.endswith('.csv'):
  132. df = pd.read_csv(input_file, encoding='utf-8') # 如果编码不对,可尝试 'gbk'
  133. elif input_file.endswith(('.xlsx', '.xls')):
  134. df = pd.read_excel(input_file)
  135. else:
  136. print("错误:不支持的文件格式。请使用 .csv, .xlsx 或 .xls 文件。")
  137. return
  138. # 检查"机型"列是否存在
  139. if model_column_name not in df.columns:
  140. print(f"错误:数据框中找不到名为 '{model_column_name}' 的列。")
  141. print(f"可用的列有:{list(df.columns)}")
  142. return
  143. # 应用提取函数
  144. print("正在解析叶轮直径和额定功率...")
  145. # 创建临时列表存储结果
  146. diameters = []
  147. powers = []
  148. for model in df[model_column_name]:
  149. result = extract_diameter_and_power(model)
  150. diameters.append(result['diameter'])
  151. powers.append(result['power_kw'])
  152. # 添加到DataFrame
  153. df["叶轮直径(m)"] = diameters
  154. df["额定功率(kW)"] = powers
  155. # 计算扫风面积
  156. print("正在计算扫风面积...")
  157. df["扫风面积(㎡)"] = df["叶轮直径(m)"].apply(calculate_swept_area)
  158. # 统计提取成功率
  159. dia_success = df["叶轮直径(m)"].notna().sum()
  160. power_success = df["额定功率(kW)"].notna().sum()
  161. swept_area_success = df["扫风面积(㎡)"].notna().sum()
  162. total_count = len(df)
  163. print(f"解析完成。")
  164. print(f"叶轮直径:成功提取 {dia_success}/{total_count} 条记录 ({dia_success/total_count*100:.1f}%)")
  165. print(f"额定功率:成功提取 {power_success}/{total_count} 条记录 ({power_success/total_count*100:.1f}%)")
  166. print(f"扫风面积:成功计算 {swept_area_success}/{total_count} 条记录 ({swept_area_success/total_count*100:.1f}%)")
  167. # 显示一些功率单位的转换情况
  168. if not df["额定功率(kW)"].empty:
  169. mw_count = (df["额定功率(kW)"] % 1000 == 0).sum()
  170. print(f"其中 {mw_count} 条记录的功率由MW单位转换得到")
  171. # 保存到新文件
  172. if output_file.endswith('.csv'):
  173. df.to_csv(output_file, index=False, encoding='utf-8-sig')
  174. else:
  175. if not output_file.endswith(('.xlsx', '.xls')):
  176. output_file = output_file + '.xlsx'
  177. df.to_excel(output_file, index=False)
  178. print(f"结果已保存至:{output_file}")
  179. # 预览结果
  180. print("\n前10条记录预览:")
  181. preview_cols = [model_column_name, "叶轮直径(m)", "额定功率(kW)", "扫风面积(㎡)"]
  182. print(df[preview_cols].head(10))
  183. # 显示一些统计信息
  184. print("\n解析结果统计:")
  185. if dia_success > 0:
  186. print(f"叶轮直径范围:{df['叶轮直径(m)'].min():.1f} - {df['叶轮直径(m)'].max():.1f} 米")
  187. if power_success > 0:
  188. print(f"额定功率范围:{df['额定功率(kW)'].min():.0f} - {df['额定功率(kW)'].max():.0f} kW")
  189. if swept_area_success > 0:
  190. print(f"扫风面积范围:{df['扫风面积(㎡)'].min():.0f} - {df['扫风面积(㎡)'].max():.0f} ㎡")
  191. # 显示最常见的功率等级
  192. if not df["额定功率(kW)"].empty:
  193. common_powers = df["额定功率(kW)"].dropna().astype(int).value_counts().head(5)
  194. print("\n最常见的5个额定功率等级(kW):")
  195. for power, count in common_powers.items():
  196. print(f" {power} kW: {count} 台")
  197. # 显示扫风面积与功率的关系示例
  198. print("\n扫风面积与功率关系示例(前5条有效记录):")
  199. valid_records = df[["叶轮直径(m)", "扫风面积(㎡)", "额定功率(kW)"]].dropna().head(5)
  200. for idx, row in valid_records.iterrows():
  201. print(f" 直径{row['叶轮直径(m)']:.1f}m → 面积{row['扫风面积(㎡)']:.0f}㎡ → 功率{row['额定功率(kW)']:.0f}kW")
  202. def calculate_additional_stats(df):
  203. """
  204. 计算额外的统计指标(可选功能)
  205. """
  206. if "叶轮直径(m)" in df.columns and "扫风面积(㎡)" in df.columns:
  207. # 计算单位扫风面积的功率密度
  208. df_valid = df.dropna(subset=["叶轮直径(m)", "扫风面积(㎡)", "额定功率(kW)"])
  209. if len(df_valid) > 0:
  210. print("\n功率密度分析(W/㎡):")
  211. df_valid["功率密度(W/㎡)"] = (df_valid["额定功率(kW)"] * 1000) / df_valid["扫风面积(㎡)"]
  212. print(f"平均功率密度:{df_valid['功率密度(W/㎡)'].mean():.2f} W/㎡")
  213. print(f"功率密度范围:{df_valid['功率密度(W/㎡)'].min():.2f} - {df_valid['功率密度(W/㎡)'].max():.2f} W/㎡")
  214. # 添加功率密度列到原始DataFrame
  215. df["功率密度(W/㎡)"] = (df["额定功率(kW)"] * 1000) / df["扫风面积(㎡)"]
  216. if __name__ == "__main__":
  217. main()
  218. # 如果需要计算功率密度,可以取消下面的注释
  219. # calculate_additional_stats(df) # 注意:需要在main函数中返回df或使用全局变量