4、选址实战:Python实现选址评分模型、数据可视化与选址报告生成

好了,前面几章我们把选址的理论框架、评分维度、数据采集都聊透了。这一章,咱们直接上代码。

说实话,我见过太多人拿着Excel表格在那手动打分,然后拍脑袋定个权重。嗯,不是说不行,但效率太低,而且容易出错。我个人习惯是:能用代码解决的,绝不手动。尤其是选址这种多维度决策问题,Python一套流程走下来,又快又准,还能自动生成报告。

4.1 选址评分模型的核心逻辑

先理一下思路。选址评分模型,说白了就是:把每个候选站点的各项指标,按照权重加权求和,得到一个总分

公式很简单:

总分 = 电价差得分 × 权重1 + 并网条件得分 × 权重2 + 负荷匹配度得分 × 权重3 + ...

但这里有个坑——不同指标的量纲不一样。电价差是元/kWh,并网距离是公里,负荷匹配度是百分比。直接加权求和?那肯定不行。所以第一步,必须做归一化处理

我的经验:归一化方法我推荐用「极差归一化」,也就是把每个指标的值映射到0-1之间。这样权重才能真正反映重要性,而不是被数值大小带偏。

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公里的直接淘汰,不参与评分。为什么?因为距离太远,电缆成本就把收益吃掉了。

注意:权重不是拍脑袋定的。我建议用层次分析法(AHP)或者专家打分法来确定。如果你懒得搞,至少也要做一次敏感性分析,看看权重变化对排名的影响。

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)
避坑指南:我曾经吃过一次亏——报告里只放了总分排名,没放各维度得分。结果领导问:「为什么A站点总分比B高,但电价差得分却低?」从那以后,我每次报告都会附带一个「各维度得分明细表」,方便追溯。

4.5 本章知识体系总览

下面这张图,是我自己总结的选址实战流程。你照着这个框架走,基本不会漏掉关键环节。

选址实战:从数据到报告的核心流程 ① 数据准备 采集/清洗/校验 ② 指标归一化 极差归一化/标准化 ③ 加权评分 权重分配/总分计算 ④ 约束过滤 硬性条件淘汰 ⑤ 数据可视化 雷达图/柱状图/热力图 ⑥ 报告生成 HTML/PDF自动输出 最终输出:选址推荐列表 + 可视化图表 + 分析报告 每个步骤都可以迭代优化,比如调整权重后重新评分 关键:权重决定方向,约束守住底线,可视化辅助决策

4.6 实战中的几个坑

代码写完了,流程也通了。但实际项目中,有几个坑我踩过,分享给你:

  • 数据质量问题:我曾经拿到一份数据,电价差那一列全是0。后来一查,是采集程序bug。所以数据清洗这步,千万别省。
  • 权重过于主观:有次项目组开会,大家争权重争了2小时。后来我直接做了个敏感性分析,发现排名对某个权重特别敏感。这才让大家达成共识。
  • 忽略地理约束:评分模型跑出来第一名,结果现场一看,那块地是基本农田。嗯,白忙活。所以约束条件一定要前置。

总结一下:选址评分模型不是一锤子买卖。你跑完第一版,拿到结果,然后去现场验证,回来再调整权重和约束条件。迭代个两三轮,才能得到真正靠谱的推荐。

好了,这一章的内容就到这。代码你可以直接拿去用,但记得根据你实际的项目数据做调整。毕竟,每个项目的侧重点不一样,权重和约束条件也得跟着变。


公众号:蓝海资料掘金营,微信deep3321