4、选址实战:Python实现选址评分模型、数据可视化与选址报告生成
好了,前面几章我们把选址的理论框架、评分维度、数据采集都聊透了。这一章,咱们直接上代码。
说实话,我见过太多人拿着Excel表格在那手动打分,然后拍脑袋定个权重。嗯,不是说不行,但效率太低,而且容易出错。我个人习惯是:能用代码解决的,绝不手动。尤其是选址这种多维度决策问题,Python一套流程走下来,又快又准,还能自动生成报告。
4.1 选址评分模型的核心逻辑
先理一下思路。选址评分模型,说白了就是:把每个候选站点的各项指标,按照权重加权求和,得到一个总分。
公式很简单:
总分 = 电价差得分 × 权重1 + 并网条件得分 × 权重2 + 负荷匹配度得分 × 权重3 + ...
但这里有个坑——不同指标的量纲不一样。电价差是元/kWh,并网距离是公里,负荷匹配度是百分比。直接加权求和?那肯定不行。所以第一步,必须做归一化处理。
4.2 Python实现:从数据到评分
下面我直接贴一段核心代码。这是我之前做某个储能项目时用的框架,稍微简化了一下,但逻辑完全一样。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 1. 加载数据(假设已经采集好)
df = pd.read_csv('site_candidates.csv')
# 2. 定义评分函数(极差归一化)
def normalize(series):
return (series - series.min()) / (series.max() - series.min())
# 3. 对每个指标做归一化
df['price_score'] = normalize(df['price_diff']) # 电价差
df['grid_score'] = 1 - normalize(df['grid_distance']) # 并网距离(越小越好)
df['load_score'] = normalize(df['load_match']) # 负荷匹配度
# 4. 定义权重(根据项目实际情况调整)
weights = {
'price_score': 0.4,
'grid_score': 0.3,
'load_score': 0.3
}
# 5. 计算总分
df['total_score'] = (
df['price_score'] * weights['price_score'] +
df['grid_score'] * weights['grid_score'] +
df['load_score'] * weights['load_score']
)
# 6. 排序输出
df_sorted = df.sort_values('total_score', ascending=False)
print(df_sorted[['site_name', 'total_score']].head(10))
你看,核心代码就这么几行。但实际项目中,我一般会再加一个约束条件过滤。比如并网距离超过5公里的直接淘汰,不参与评分。为什么?因为距离太远,电缆成本就把收益吃掉了。
4.3 数据可视化:让结果一目了然
评分模型跑完了,接下来就是可视化。我个人习惯用雷达图和柱状图来展示。雷达图能直观看到每个站点的优劣势,柱状图则方便对比总分。
下面这段代码,生成一个雷达图:
import plotly.express as px
import plotly.graph_objects as go
# 选取前5个站点
top5 = df_sorted.head(5)
# 雷达图
fig = go.Figure()
for _, row in top5.iterrows():
fig.add_trace(go.Scatterpolar(
r=[row['price_score'], row['grid_score'], row['load_score']],
theta=['电价差', '并网条件', '负荷匹配'],
fill='toself',
name=row['site_name']
))
fig.update_layout(
polar=dict(radialaxis=dict(visible=True, range=[0, 1])),
title='Top5站点各维度评分对比'
)
fig.show()
嗯,这里要注意一点:雷达图的维度不要太多。超过6个维度,图就变成蜘蛛网了,根本看不清。我一般只选3-5个核心维度。
另外,我还会画一个热力图,看看各指标之间的相关性。比如电价差和负荷匹配度是不是正相关?如果是,那说明这个区域的储能需求确实旺盛。
# 相关性热力图
corr = df[['price_diff', 'grid_distance', 'load_match']].corr()
sns.heatmap(corr, annot=True, cmap='coolwarm')
plt.title('指标相关性分析')
plt.show()
4.4 选址报告生成:自动化输出
评分和可视化都搞定了,最后一步是生成报告。我习惯用Jinja2模板配合PDFkit或者ReportLab,自动生成一份带图表、带评语的PDF报告。
但这里为了演示,我用一个更轻量的方式——直接生成HTML报告,然后转PDF。
from jinja2 import Template
# 定义HTML模板
template_str = """
<h2>储能选址评分报告</h2>
<p>生成日期:{{ date }}</p>
<h3>Top5站点排名</h3>
<table border="1">
<tr><th>站点名称</th><th>总分</th><th>电价差得分</th><th>并网得分</th><th>负荷得分</th></tr>
{% for site in top5 %}
<tr>
<td>{{ site.name }}</td>
<td>{{ site.total }}</td>
<td>{{ site.price }}</td>
<td>{{ site.grid }}</td>
<td>{{ site.load }}</td>
</tr>
{% endfor %}
</table>
<h3>推荐站点分析</h3>
<p>综合评分最高的站点是:<strong>{{ best_site }}</strong></p>
<p>建议优先开展现场踏勘和电网接入申请。</p>
"""
# 渲染模板
template = Template(template_str)
html_content = template.render(
date='2025-01-15',
top5=top5_dict_list,
best_site=top5_dict_list[0]['name']
)
# 保存为HTML文件
with open('选址报告.html', 'w', encoding='utf-8') as f:
f.write(html_content)
4.5 本章知识体系总览
下面这张图,是我自己总结的选址实战流程。你照着这个框架走,基本不会漏掉关键环节。
4.6 实战中的几个坑
代码写完了,流程也通了。但实际项目中,有几个坑我踩过,分享给你:
- 数据质量问题:我曾经拿到一份数据,电价差那一列全是0。后来一查,是采集程序bug。所以数据清洗这步,千万别省。
- 权重过于主观:有次项目组开会,大家争权重争了2小时。后来我直接做了个敏感性分析,发现排名对某个权重特别敏感。这才让大家达成共识。
- 忽略地理约束:评分模型跑出来第一名,结果现场一看,那块地是基本农田。嗯,白忙活。所以约束条件一定要前置。
总结一下:选址评分模型不是一锤子买卖。你跑完第一版,拿到结果,然后去现场验证,回来再调整权重和约束条件。迭代个两三轮,才能得到真正靠谱的推荐。
好了,这一章的内容就到这。代码你可以直接拿去用,但记得根据你实际的项目数据做调整。毕竟,每个项目的侧重点不一样,权重和约束条件也得跟着变。
公众号:蓝海资料掘金营,微信deep3321