4. 用户响应行为建模:价格弹性矩阵、消费者心理学模型、用户舒适度约束

好,咱们进入第四章。说实话,这一章是整个需求响应算法里最“软”的部分,但也是最关键的部分。你想想看,电网侧的约束、市场规则,这些都是硬邦邦的数学公式。可用户呢?用户是人,人的行为是有情绪的、有惯性的、有舒适度底线的。

我刚开始做需求响应项目时,犯过一个错误。我假设用户都是“理性经济人”——电价涨了,他就自动少用电。结果呢?实际数据啪啪打脸。用户根本没那么敏感,或者说,他的敏感是有条件的。今天咱们就把这块硬骨头啃下来。

4.1 价格弹性矩阵:从“一刀切”到“精细化”

价格弹性,说白了就是“电价变化1%,用电量变化百分之几”。但这里有个坑——不同时段的弹性是不一样的。

举个例子:

  • 峰时段:用户已经开空调了,你电价涨20%,他可能也就忍了,弹性很小。
  • 谷时段:用户本来就在睡觉,电价再降,他也起不来开洗衣机,弹性几乎为零。
  • 平时段:用户有选择空间,弹性相对大一些。

所以,我们不能用一个数字描述所有时段的弹性。得用矩阵。

价格弹性矩阵:一个 n×n 的矩阵,n 是时段数。对角线元素是自弹性(本时段电价对本时段用电的影响),非对角线元素是交叉弹性(其他时段电价对本时段用电的影响)。

数学上长这样:

E = [[ε₁₁, ε₁₂, ..., ε₁ₙ],
     [ε₂₁, ε₂₂, ..., ε₂ₙ],
     ...
     [εₙ₁, εₙ₂, ..., εₙₙ]]

其中 εᵢⱼ = (∂Qᵢ / ∂Pⱼ) × (Pⱼ / Qᵢ)。

我个人习惯用对数线性模型来估计这个矩阵。为什么?因为稳定。线性模型容易跑出负的用电量,对数模型不会。

import numpy as np
import pandas as pd

def estimate_elasticity_matrix(price_data, demand_data):
    """
    用最小二乘法估计价格弹性矩阵
    price_data: DataFrame, 每列是一个时段的价格
    demand_data: DataFrame, 每列是对应时段的用电量
    """
    n_periods = price_data.shape[1]
    E = np.zeros((n_periods, n_periods))
    
    for i in range(n_periods):
        # 对每个时段的需求,用所有时段的价格做回归
        log_demand = np.log(demand_data.iloc[:, i])
        log_prices = np.log(price_data)
        log_prices = sm.add_constant(log_prices)
        
        model = sm.OLS(log_demand, log_prices).fit()
        # 系数就是弹性
        E[i, :] = model.params[1:]  # 去掉常数项
    
    return E

避坑指南:我曾经用这个模型跑一个工业用户的数据,结果交叉弹性全是负的。后来发现,这个用户的生产流程是连续的,峰时段电价高了,他只能把生产挪到谷时段,所以交叉弹性应该是正的(替代效应)。嗯,这里要注意——交叉弹性的符号取决于用户类型。居民用户一般是负的(互补效应),工业用户可能是正的(替代效应)。

4.2 消费者心理学模型:用户不是机器人

价格弹性矩阵是纯数学的,但用户的行为里藏着心理学。我总结了三类常见的心理效应:

  1. 阈值效应:电价变化必须超过某个阈值,用户才会反应。比如电价涨5%,用户可能没感觉;涨20%,他才开始关灯。
  2. 惯性效应:用户不会立刻响应。今天电价涨了,他可能拖到明天才调整。这个滞后时间,短的几小时,长的好几天。
  3. 饱和效应:用户的响应是有上限的。你电价涨到天上去,他最多也就关掉非必要负荷,不可能把冰箱也拔了。

怎么建模?我常用一个带死区的S型曲线模型:

def psychological_response(price_change, threshold=0.1, max_response=0.3, steepness=10):
    """
    消费者心理学响应模型
    price_change: 电价变化比例(如0.2表示涨20%)
    threshold: 响应阈值
    max_response: 最大响应比例
    steepness: 曲线陡峭程度
    """
    if abs(price_change) < threshold:
        return 0  # 死区内,无响应
    
    # S型曲线:sigmoid函数
    x = (abs(price_change) - threshold) * steepness
    response = max_response / (1 + np.exp(-x))
    
    # 根据涨价还是降价,决定响应方向
    return response if price_change > 0 else -response

关键参数:阈值、最大响应、滞后时间。这三个参数,我建议用历史数据做网格搜索来标定。别拍脑袋定,我吃过这个亏。

4.3 用户舒适度约束:别把用户逼急了

这是最容易被忽视的一点。很多算法只想着“怎么让用户少用电”,却忘了问“用户愿不愿意”。

我参与过一个项目,算法把空调温度设到30度,用户直接手动override了。为什么?因为舒适度被突破了。

常见的舒适度约束包括:

  • 温度范围:夏季空调,一般24-28度是可接受的。低于24度或高于28度,用户会投诉。
  • 光照强度:调光控制,不能低于某个照度值。
  • 设备启停次数:空调频繁启停,用户会烦。一般每小时不超过4次。
  • 等待时间:热水器、洗衣机,用户能接受的最长等待时间。

数学上,这些约束可以写成:

T_min ≤ T(t) ≤ T_max          # 温度约束
L(t) ≥ L_min                   # 光照约束
∑ on_off_events(t) ≤ N_max    # 启停次数约束
wait_time ≤ W_max              # 等待时间约束

注意:舒适度约束不是死的。不同用户、不同场景,容忍度不一样。我建议在系统中留一个“用户偏好配置”接口,让用户自己调。别替用户做决定。

4.4 三者如何协同?一张图说清楚

价格弹性矩阵、心理学模型、舒适度约束,这三者不是孤立的。它们的关系是这样的:

用户响应行为建模核心逻辑 电价信号 价格弹性矩阵 自弹性 + 交叉弹性 消费者心理学模型 阈值效应 · 惯性效应 · 饱和效应 用户舒适度约束 温度 · 光照 · 启停次数 · 等待时间 用户实际响应 反馈学习

你看这个流程:电价信号进来,先经过价格弹性矩阵算出一个“理论响应量”。但这个量是纯数学的,用户不一定买账。所以再经过心理学模型,加上阈值、惯性、饱和这些“人性化”的修正。最后,还要检查舒适度约束——如果响应方案让用户不舒服,那就得调整。

这三层走完,才是用户真正会执行的响应行为。

4.5 实战中的几个坑

最后,分享几个我踩过的坑:

  • 数据粒度不够:价格弹性矩阵需要至少1年的历史数据,而且粒度要到15分钟。如果只有小时级数据,弹性估计会偏差很大。
  • 忽略用户异质性:别把所有用户当成一类。居民、商业、工业,弹性完全不同。我建议至少分3类建模。
  • 舒适度约束太硬:有些约束可以软化的。比如温度,不是非得24-28度,可以设一个“惩罚函数”,超出范围就加惩罚项,而不是直接禁止。
  • 忘了反馈:用户的行为会改变他的习惯。今天响应了,明天可能就不响应了。模型需要在线更新。

我的建议:刚开始做的时候,别追求完美。先用一个简单的自弹性模型跑起来,然后逐步加入交叉弹性、心理学模型、舒适度约束。迭代着来,比一次性搞个大而全的模型靠谱得多。

好了,这一章就到这儿。用户响应行为建模,说白了就是“理解用户、尊重用户、引导用户”。别想着控制用户,那行不通。


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