小能豆

使用 scipy NonlinearConstraint 在 pandas 数据框上求解非线性方程

py

我正在尝试求解方程组:

1.png

其中 a、b 和 c 是来自 pandas 数据框的列。我以前使用 Excel,通过更改其他列(函数)的值,在一列(残差)中使用 go seak 运行宏,但我不知道如何在 Python 中执行此操作,

我尝试过 fsolve,但结果总是我最初的猜测,所以,我猜它需要一些界限,但 fsolve 不允许引入界限,这就是我尝试 scipyn.NonlinearConstraint 的原因。下面是需要解决的数据集和函数:

import numpy as np
import pandas as pd
import scipy.optimize as opt
from scipy.optimize import NonlinearConstraint
a = np.linspace(300,400,30) 
b = np.random.randint(700,18000,30) 
c = np.random.uniform(1.4,4.0,30) 
df = pd.DataFrame({'A':a, 'B':b, 'C':c})


def func(zGuess,*Params):
    x,y,z = zGuess
    a,b,c = Params

    eq_1 = (((3.47-np.log10(y))**2+(np.log10(c)+1.22)**2)**0.5) - x
    eq_2 = ((a/101.32) * (101.32/b)** z) - y
    eq_3 = (0.381 * x + 0.05 * (b/101.32) -0.15) - z
    return eq_1,eq_2,eq_3


zGuess = np.array([2.6,20.2,0.92])
up_bound = (9,np.inf,1.1)
low_bound = (0,-np.inf,0)

这里我尝试用 fsolve 来解决:

df['x'],df['y'],df['z'] = zip(*df.apply(lambda x: opt.fsolve(func,zGuess,args=(x['A'],x['B'],x['C'])),1) )

这里具有非线性约束:

df['x'],df['y'],df['z'] = zip(*df.apply(lambda x: NonlinearConstraint(func(zGuess,x['A'],x['B'],x['C']),low_bound,up_bound),1) )

但使用非线性约束时会出现错误:

TypeError: zip 参数 #1 必须支持迭代

我甚至尝试最小化熊猫数据框:

def func2(zGuess,*Params):
    x,y = zGuess
    a,b,c,n = Params

    eq_1 = (((3.47-np.log10(y))**2+(np.log10(c)+1.22)**2)**0.5) - x
    eq_2 = ((a/101.32) * (101.32/b)** n) - y

    return eq_1,eq_2,eq_3



zGuess2 = np.array([2.6,20.2])
n = np.linspace(0,1.,500)
for i in n:
    df['x'],df['y'] = zip(*df.apply(lambda x: opt.fsolve(func2,zGuess2,args=(x['a'],x['b'],x['c'],i)),1))
    df['n_calc'] = df.apply(lambda x: (0.381 * x['x'] + 0.05 * (x['b']/101.32) - 0.15),1)
    res = np.abs(df['n_calc'] - i)
    if res <= 1e-5:
        n_solver = i
        df['n_solver'].append(n_solver)

我从来没有想过像 excel 中的 go seak 这样简单的事情在 python 中执行会如此困难,我真的需要一些帮助,拜托。


阅读 18

收藏
2024-12-13

共2个答案

小能豆

你提出的问题是如何在 Python 中解决类似于 Excel 中 Goal Seek 的问题,其中目标是通过调整一组参数(如 abc)来找到使某个公式的结果达到特定值的参数组合。

在 Python 中,我们可以使用 scipy.optimize 模块来求解这样的非线性方程组,并且我们可以应用约束条件。你提到的 fsolve 是一个非常合适的工具,但它确实不会处理约束,因此我们需要其他方法来处理这个问题,特别是当你需要根据约束来寻找解时。

解决方案

我们可以采用以下两种方法来处理你的问题:

  1. 使用 scipy.optimize.fsolve 解决无约束方程
  2. fsolve 可以帮助我们求解方程组,但是它不会直接处理边界约束。如果不需要约束条件,那么你可以直接使用 fsolve

  3. 使用 scipy.optimize.minimize 来解决带约束的优化问题

  4. minimize 函数允许我们指定约束条件,并通过最小化目标函数来找到解。我们可以利用最小化的方式来“逼近”目标值,从而间接解决 Goal Seek 问题。

示例代码:

我们先从不使用约束的 fsolve 开始,然后再展示如何使用 minimize 处理带有约束的优化问题。

1. 使用 fsolve 求解无约束方程

import numpy as np
import pandas as pd
import scipy.optimize as opt

# 示例数据
a = np.linspace(300, 400, 30)
b = np.random.randint(700, 18000, 30)
c = np.random.uniform(1.4, 4.0, 30)
df = pd.DataFrame({'A': a, 'B': b, 'C': c})

# 定义方程组
def func(zGuess, a, b, c):
    x, y, z = zGuess
    eq_1 = (((3.47 - np.log10(y))**2 + (np.log10(c) + 1.22)**2) ** 0.5) - x
    eq_2 = ((a / 101.32) * (101.32 / b) ** z) - y
    eq_3 = (0.381 * x + 0.05 * (b / 101.32) - 0.15) - z
    return eq_1, eq_2, eq_3

# 初始猜测
zGuess = np.array([2.6, 20.2, 0.92])

# 使用 fsolve 解决方程组
df['x'], df['y'], df['z'] = zip(*df.apply(lambda row: opt.fsolve(func, zGuess, args=(row['A'], row['B'], row['C'])), axis=1))

# 查看结果
print(df[['x', 'y', 'z']])

2. 使用 minimize 带约束的优化问题

如果你需要使用边界约束(如 x 在某个范围内),我们可以使用 scipy.optimize.minimize 来处理带有约束的优化问题。

import numpy as np
import pandas as pd
import scipy.optimize as opt

# 示例数据
a = np.linspace(300, 400, 30)
b = np.random.randint(700, 18000, 30)
c = np.random.uniform(1.4, 4.0, 30)
df = pd.DataFrame({'A': a, 'B': b, 'C': c})

# 定义方程组
def func(zGuess, a, b, c):
    x, y, z = zGuess
    eq_1 = (((3.47 - np.log10(y))**2 + (np.log10(c) + 1.22)**2) ** 0.5) - x
    eq_2 = ((a / 101.32) * (101.32 / b) ** z) - y
    eq_3 = (0.381 * x + 0.05 * (b / 101.32) - 0.15) - z
    return eq_1**2 + eq_2**2 + eq_3**2  # 目标函数:最小化误差的平方和

# 初始猜测
zGuess = np.array([2.6, 20.2, 0.92])

# 设置边界
bounds = [(0, 9), (0, np.inf), (0, 1.1)]

# 使用 minimize 进行优化
def solve_for_row(row):
    result = opt.minimize(func, zGuess, args=(row['A'], row['B'], row['C']), bounds=bounds)
    if result.success:
        return result.x
    else:
        return [np.nan, np.nan, np.nan]

# 对每一行数据应用优化
df['x'], df['y'], df['z'] = zip(*df.apply(solve_for_row, axis=1))

# 查看结果
print(df[['x', 'y', 'z']])

解释:

  1. fsolve 解决无约束问题fsolve 用来求解你给定的非线性方程组。你提供初始猜测,fsolve 会返回使方程组为零的解。
  2. minimize 处理约束minimize 允许你设定参数的边界约束(如 (0, 9)(0, np.inf)),并通过最小化目标函数来找到解。这里我们定义了一个目标函数,目标是使得方程组的误差最小(平方和最小)。

总结:

  • 如果你的方程没有约束条件,使用 fsolve 是一个直接有效的选择。
  • 如果有约束条件(如参数范围限制),则 minimize 是一个更灵活的选择。
  • 通过 apply 方法,我们能够将每行数据传递给优化函数,并获得结果。

这些方法可以帮助你在 Python 中实现类似 Excel Goal Seek 的功能。希望这能解决你的问题!

2024-12-13
小能豆

我将忽略您问题中的 pandas 部分,只讨论优化部分。忽略所有 pandas 噪音,您的问题基本上是如何求解具有简单变量界限的方程。这个问题已经回答过好几次了,例如请参见此处。

以下是一些提示:

  • 您的界限没有意义,例如对数仅针对正值定义,而不是针对零定义。
  • 请熟悉 np.ndarrays。这将使您使用 scipy.optimize 时更加轻松。
  • 您的 NonlinearConstraint 也没有意义。这是一个可以传递给 的对象scipy.optimize.minimize。它不是可调用的,也不是优化方法。
  • 如果您想要求解一个对变量有一定限制的方程,则需要将问题重新表述为非线性优化问题,并通过 求解scipy.optimize.minimize,有关详细信息,请参阅上面链接的答案。请注意,我已将您的func答案更改为返回 np.ndarray。

以下是一个可解决数据框中每行方程的工作版本。您可以随意重写它,以便在里面使用它df.apply()

from scipy.optimize import minimize

def func(zz, *params):
    x,y,z = zz
    a,b,c = params

    eq_1 = (((3.47-np.log10(y))**2+(np.log10(c)+1.22)**2)**0.5) - x
    eq_2 = ((a/101.32) * (101.32/b)** z) - y
    eq_3 = (0.381 * x + 0.05 * (b/101.32) -0.15) - z
    return np.array((eq_1,eq_2,eq_3))

bounds = [(1e-6, None), (1e-6, None), (1e-6, None)]
zGuess = np.array([2.6,20.2,0.92])

# create the new columns
df[["x", "y", "z"]] = np.zeros((30, 3))

for i, row in df.iterrows():
    params = row.values[:3]
    res = minimize(lambda x: np.sum(func(x, *params)**2), zGuess, bounds=bounds)
    # res.x contains your solution
    # let's write the values into the dataframe
    for k, val in zip(['x', 'y', 'z'], res.x):
        df.loc[i, k] = val

我从来没有想过像 excel 中的 go seek 这样简单的事情在 python 中执行会如此困难

在没有阅读文档的情况下混合几种不同的概念会使任何编程语言中的每项任务都变得困难。

2024-12-13