Search code examples
pythonrdatasetrootsolver

How to find a root of a dynamic function based in columns of a dataset using Python


I'm beginner in python and I need to translate some code in R to Python.

I need to find one root per row in a dataset based in a dynamic function, the code in R is:

library(rootSolve
library(dplyr)
library(plyr)

 dataset = data.frame(A = c(10,20,30),B=c(20,10,40), FX = c("A+B-x","A-B+x","A*B-x"))

 sol<- adply(dataset,1, summarize,
               solution_0= uniroot.all(function(x)(eval(parse(text=as.character(FX),dataset))),lower = -10000, upper = 10000, tol = 0.00001))

This code return [30,-10,1200] as a solution for each row.

In python I read a documentation of optimize of sciPy package but i don't found a code that's work for me:

I tried a solutions like that below, but without sucess:

import pandas as pd
from scipy.optimize import fsolve as fs

data = {'A': [10,20,30],
        'B': [20,10,40],
        'FX': ["A+B-x","A-B+x","A*B-x"]}
df = pd.DataFrame(data)

def func(FX):
    return(exec(FX))

fs(func(df.FX),x0=0,args=df) 

Someone have idea how to solve this?

Very Thanks.


Solution

  • SymPy is a symbolic math library for Python. Your question can be solved as:

    import pandas as pd
    from sympy import Symbol, solve
    from sympy.parsing.sympy_parser import parse_expr
    
    data = {'A': [10,20,30],
            'B': [20,10,40],
            'FX': ["A+B-x","A-B+x","A*B-x"]}
    
    df = pd.DataFrame(data)
    
    x = Symbol("x", real=True)
    
    for index, row in df.iterrows():
        F = parse_expr(row['FX'], local_dict={'A': row['A'], 'B': row['B'], 'x':x})
        print (row['A'], row['B'], row['FX'], "-->", F, "-->", solve(F, x))
    

    This outputs:

    10 20 A+B-x --> 30 - x --> [30]
    20 10 A-B+x --> x + 10 --> [-10]
    30 40 A*B-x --> 1200 - x --> [1200]
    

    Note that SymPy's solve returns a list of solutions. If you are sure there is always exactly one solution, just use solve(F, x)[0]. (Remember that unlike R, Python always starts indexing with 0.)

    With list comprehension, you could write the solution as:

    sol = [ solve(parse_expr(row['FX'], local_dict={'A': row['A'], 'B': row['B'], 'x':x}),
                  x)[0] for _, row in df.iterrows() ]
    

    If you have many columns, you can also create the dictionary with a loop: dict({c:row[c] for c in df.columns}, **{'x':x}) ). The weird ** syntax is needed if you want to combine the dictionaries inside the list comprehension. See this post about the union of dictionaries.

    cols = df.columns # change this if you won't need all columns
    sol = [ solve(parse_expr(row['FX'],
                             local_dict=dict({c:row[c] for c in cols}, **{'x':x}) ),
                  x)[0].evalf() for _, row in df.iterrows() ]
    

    PS: SymPy normally keeps the solutions in a symbolic form because it prefers exact expressions. When there are e.g. fractions or square roots, they are not evaluated immediately. To get the evaluated form, use evalf() as in solve(F, x)[0].evalf().