| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- import pandas as pd
- import re
- import math
- def extract_diameter_and_power(model_str):
- """
- 从风机机型字符串中提取叶轮直径(单位:米)和额定功率(单位:kW)。
- 返回一个字典:{'diameter': 直径, 'power_kw': 功率}
- 如果无法确定则对应值为None。
- """
- if not isinstance(model_str, str):
- return {'diameter': None, 'power_kw': None}
-
- s = model_str.strip().upper()
-
- # ----- 第一步:找出所有可能的数字 -----
- all_numbers = []
- matches = re.findall(r'\d+\.?\d*', s)
- for num_str in matches:
- try:
- num = float(num_str)
- all_numbers.append(num)
- except ValueError:
- continue
-
- if len(all_numbers) < 2:
- # 如果没有至少两个数字,无法区分直径和功率
- return {'diameter': None, 'power_kw': None}
-
- # ----- 第二步:根据特征区分直径和功率 -----
- diameter_candidates = []
- power_candidates = []
-
- for num in all_numbers:
- # 直径的特征:通常为2-3位整数,范围在50-300米之间
- if 20 <= num <= 400 and num > 10: # 放宽下限到20,确保包含小直径机型
- # 直径通常接近整数,且数值相对较小
- if abs(num - round(num)) < 0.1: # 接近整数
- diameter_candidates.append(num)
- elif 50 <= num <= 300: # 在典型直径范围内的小数也可能是直径
- diameter_candidates.append(num)
-
- # 功率的特征:
- # 1. 兆瓦级的小数 (如1.5, 2.0, 3.6, 6.7)
- # 2. 百位或千位整数 (如1500, 2000, 3000, 5000)
- # 3. 万位整数 (如10000, 12000)
-
- # 判断是否为兆瓦级功率(常见的小数功率)
- if 0.5 <= num <= 20 and '.' in str(num):
- power_candidates.append(num * 1000) # 转换为kW
-
- # 判断是否为千瓦级功率
- elif num >= 100: # 功率通常至少100kW以上
- # 典型功率值范围
- if 100 <= num <= 30000:
- power_candidates.append(num)
-
- # ----- 第三步:特殊处理MW单位标识 -----
- # 如果字符串中包含"MW"标识,可以更准确地提取功率
- if 'MW' in s:
- # 寻找靠近"MW"的数字
- mw_pattern = r'(\d+\.?\d*)\s*MW'
- mw_matches = re.findall(mw_pattern, s)
- for mw_str in mw_matches:
- try:
- mw_value = float(mw_str)
- # 转换为kW
- kw_value = mw_value * 1000
- if kw_value not in power_candidates:
- power_candidates.append(kw_value)
- except ValueError:
- pass
-
- # ----- 第四步:决策逻辑 -----
- result = {'diameter': None, 'power_kw': None}
-
- # 1. 直径决策
- if diameter_candidates:
- # 优先选择在典型直径范围(50-200)内的整数
- typical_diameters = [d for d in diameter_candidates if 50 <= d <= 200]
- if typical_diameters:
- # 选择第一个(通常字符串中先出现的是直径)
- result['diameter'] = typical_diameters[0]
- else:
- # 如果不在典型范围,选择最小的(假设直径通常比功率数值小)
- result['diameter'] = min(diameter_candidates)
-
- # 2. 功率决策
- if power_candidates:
- # 优先选择通过MW标识找到的功率
- mw_based_power = [p for p in power_candidates if p in [num * 1000 for num in all_numbers if '.' in str(num)]]
- if mw_based_power:
- result['power_kw'] = mw_based_power[0]
- else:
- # 否则选择最大的(假设功率数值通常比直径大)
- # 但需要排除明显是直径的值
- filtered_power = [p for p in power_candidates if p != result['diameter']]
- if filtered_power:
- # 对于功率,如果是整数,优先选择常见的功率等级
- common_powers = [1500, 2000, 2500, 3000, 5000, 6000, 10000, 12000]
- for cp in common_powers:
- if cp in [int(p) for p in filtered_power if abs(p - round(p)) < 0.1]:
- result['power_kw'] = cp
- break
- if result['power_kw'] is None:
- result['power_kw'] = max(filtered_power)
-
- # ----- 第五步:如果决策失败,尝试基于位置的简单逻辑 -----
- if result['diameter'] is None or result['power_kw'] is None:
- if len(all_numbers) >= 2:
- # 假设第一个数字是直径,第二个是功率(常见格式:直径-功率)
- if 20 <= all_numbers[0] <= 300:
- result['diameter'] = all_numbers[0]
-
- # 判断第二个数字是否为功率
- if len(all_numbers) > 1:
- second_num = all_numbers[1]
- # 如果是小数,很可能是兆瓦级功率
- if '.' in str(second_num) and 0.5 <= second_num <= 20:
- result['power_kw'] = second_num * 1000
- elif second_num >= 100 and second_num <= 30000:
- result['power_kw'] = second_num
-
- return result
- def calculate_swept_area(diameter):
- """
- 计算扫风面积
- 公式:扫风面积 = π × (叶轮直径/2)²
- 单位:平方米(㎡)
- """
- if diameter is None or pd.isna(diameter):
- return None
-
- try:
- # 使用高精度的π值
- radius = diameter / 2.0
- swept_area = math.pi * (radius ** 2)
- return round(swept_area, 2) # 保留两位小数
- except (TypeError, ValueError):
- return None
- def main():
- # ---------- 配置区:请根据您的实际文件修改 ----------
- input_file = f"./data/全部机型功率曲线_含标准类型.csv" # 输入文件名,支持 .csv, .xlsx, .xls
- output_file = f"./output/全部机型功率曲线_含标准类型_解析结果.csv" # 输出文件名
- model_column_name = "标准机型" # 包含机型信息的列名
- # -------------------------------------------------
-
- # 读取文件
- if input_file.endswith('.csv'):
- df = pd.read_csv(input_file, encoding='utf-8') # 如果编码不对,可尝试 'gbk'
- elif input_file.endswith(('.xlsx', '.xls')):
- df = pd.read_excel(input_file)
- else:
- print("错误:不支持的文件格式。请使用 .csv, .xlsx 或 .xls 文件。")
- return
-
- # 检查"机型"列是否存在
- if model_column_name not in df.columns:
- print(f"错误:数据框中找不到名为 '{model_column_name}' 的列。")
- print(f"可用的列有:{list(df.columns)}")
- return
-
- # 应用提取函数
- print("正在解析叶轮直径和额定功率...")
-
- # 创建临时列表存储结果
- diameters = []
- powers = []
-
- for model in df[model_column_name]:
- result = extract_diameter_and_power(model)
- diameters.append(result['diameter'])
- powers.append(result['power_kw'])
-
- # 添加到DataFrame
- df["叶轮直径(m)"] = diameters
- df["额定功率(kW)"] = powers
-
- # 计算扫风面积
- print("正在计算扫风面积...")
- df["扫风面积(㎡)"] = df["叶轮直径(m)"].apply(calculate_swept_area)
-
- # 统计提取成功率
- dia_success = df["叶轮直径(m)"].notna().sum()
- power_success = df["额定功率(kW)"].notna().sum()
- swept_area_success = df["扫风面积(㎡)"].notna().sum()
- total_count = len(df)
-
- print(f"解析完成。")
- print(f"叶轮直径:成功提取 {dia_success}/{total_count} 条记录 ({dia_success/total_count*100:.1f}%)")
- print(f"额定功率:成功提取 {power_success}/{total_count} 条记录 ({power_success/total_count*100:.1f}%)")
- print(f"扫风面积:成功计算 {swept_area_success}/{total_count} 条记录 ({swept_area_success/total_count*100:.1f}%)")
-
- # 显示一些功率单位的转换情况
- if not df["额定功率(kW)"].empty:
- mw_count = (df["额定功率(kW)"] % 1000 == 0).sum()
- print(f"其中 {mw_count} 条记录的功率由MW单位转换得到")
-
- # 保存到新文件
- if output_file.endswith('.csv'):
- df.to_csv(output_file, index=False, encoding='utf-8-sig')
- else:
- if not output_file.endswith(('.xlsx', '.xls')):
- output_file = output_file + '.xlsx'
- df.to_excel(output_file, index=False)
-
- print(f"结果已保存至:{output_file}")
-
- # 预览结果
- print("\n前10条记录预览:")
- preview_cols = [model_column_name, "叶轮直径(m)", "额定功率(kW)", "扫风面积(㎡)"]
- print(df[preview_cols].head(10))
-
- # 显示一些统计信息
- print("\n解析结果统计:")
- if dia_success > 0:
- print(f"叶轮直径范围:{df['叶轮直径(m)'].min():.1f} - {df['叶轮直径(m)'].max():.1f} 米")
-
- if power_success > 0:
- print(f"额定功率范围:{df['额定功率(kW)'].min():.0f} - {df['额定功率(kW)'].max():.0f} kW")
-
- if swept_area_success > 0:
- print(f"扫风面积范围:{df['扫风面积(㎡)'].min():.0f} - {df['扫风面积(㎡)'].max():.0f} ㎡")
-
- # 显示最常见的功率等级
- if not df["额定功率(kW)"].empty:
- common_powers = df["额定功率(kW)"].dropna().astype(int).value_counts().head(5)
- print("\n最常见的5个额定功率等级(kW):")
- for power, count in common_powers.items():
- print(f" {power} kW: {count} 台")
-
- # 显示扫风面积与功率的关系示例
- print("\n扫风面积与功率关系示例(前5条有效记录):")
- valid_records = df[["叶轮直径(m)", "扫风面积(㎡)", "额定功率(kW)"]].dropna().head(5)
- for idx, row in valid_records.iterrows():
- print(f" 直径{row['叶轮直径(m)']:.1f}m → 面积{row['扫风面积(㎡)']:.0f}㎡ → 功率{row['额定功率(kW)']:.0f}kW")
- def calculate_additional_stats(df):
- """
- 计算额外的统计指标(可选功能)
- """
- if "叶轮直径(m)" in df.columns and "扫风面积(㎡)" in df.columns:
- # 计算单位扫风面积的功率密度
- df_valid = df.dropna(subset=["叶轮直径(m)", "扫风面积(㎡)", "额定功率(kW)"])
-
- if len(df_valid) > 0:
- print("\n功率密度分析(W/㎡):")
- df_valid["功率密度(W/㎡)"] = (df_valid["额定功率(kW)"] * 1000) / df_valid["扫风面积(㎡)"]
-
- print(f"平均功率密度:{df_valid['功率密度(W/㎡)'].mean():.2f} W/㎡")
- print(f"功率密度范围:{df_valid['功率密度(W/㎡)'].min():.2f} - {df_valid['功率密度(W/㎡)'].max():.2f} W/㎡")
-
- # 添加功率密度列到原始DataFrame
- df["功率密度(W/㎡)"] = (df["额定功率(kW)"] * 1000) / df["扫风面积(㎡)"]
- if __name__ == "__main__":
- main()
- # 如果需要计算功率密度,可以取消下面的注释
- # calculate_additional_stats(df) # 注意:需要在main函数中返回df或使用全局变量
|