editable 中的初始化和重新计算 plotly dash table

Initialization and recalculation in editable plotly dash table

我目前正在从事一个启用交互式股票估值的项目。这需要用户通过输入股票代码来 select 股票,然后该用户应该能够调整股票驱动因素并观察这些变化对公司估值的影响。

为了实现这一点,必须首先用股票信息填充 table,然后根据用户编辑的变量更新计算。理想情况下,可以根据示例 'Updating Columns of the Same Table' here 更新 table 本身的计算。但是,就我而言,我正在努力将 table 的初始化与 stock selection 的输出结合起来,并在用户修改 table 时更新 table 的计算table的内容。

我在下面整理了一个非常简单的删节示例。在这种情况下,我认为将股票数据和计算输出分成两个单独的 table 可能会更简单。这也是我想做的事情,它代表了一个比将所有东西都集中在一个地方更广泛的用例 table.

我对我的代码做了一些注释,希望我的逻辑足够清晰。但是代码的基本概述如下:

(i) 请求用户输入代码(本例中为 stock1 或 stock2)。

(ii) 显示代码,如果代码不正确则显示错误消息。

(iii) 在中间数据框中存储股票数据(按照示例 1 here。)

(iv) 使用当前 selected 股票的 selected 股票数据填充 FCFf table。

(v) 使用来自 FCFf 数据的数据计算现值

我的问题似乎出现在 (v) 中:我的方法涉及将库存数据从 (iii) 加载到 stock_df,将 fcff 数据从 (iv) 加载到 fcff_df,并用 fcff_df 中的字段覆盖 stock_df 中的 fcff 字段。不幸的是,在手动更改任何 FCFf 值(例如 fcff_fy3 列中的值)后,估值 table 不会更新:相反,我收到错误

TypeError: ufunc 'true_divide' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

令我震惊的是,也许用户输入没有被解释为与单元格具有相同的类型,而是被视为字符串?

但是,无论如何,amy的做法似乎有点反常,不知道有没有更好的解决办法?

请注意,我还希望能够计算出对单个 table 进行更改的时间。例如,如果我将估值 table 和 FCFf tables 合并为一个,并且还添加了贴现率 'r',用于 do_pv,作为 editable 字段,我该怎么做?感谢您的帮助。

示例数据和代码:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
import pandas as pd

from dash_table.Format import Format, Scheme, Sign, Symbol

df = pd.DataFrame(data = { 'ticker' :  ['stock1', 'stock2'], 'r' : [0.1, 0.2], 'fcff_fy1' : [7902, 9409] , 'fcff_fy2' : [13912, 68969], 'fcff_fy3' : [11309, 7154], 'fcff_fy4' : [13912, 68969], 'fcff_fy5' : [76158,   84090]   })
fcff_cols = ['fcff_fy1', 'fcff_fy2', 'fcff_fy3', 'fcff_fy4', 'fcff_fy5']
valuation_cols = ['Item', 'Valuation']

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config.suppress_callback_exceptions = True
app.css.append_css({
    "external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"
})

app.layout = html.Div([
    html.H1('Stock Valuation'),
    html.H5('Choose stock from stock1, stock2'),
    dcc.Input(id='stock-id', value='stock1', type='text'),  #human text input of stock ticker
    html.Div(id='my-tick'), # Output to identify and inform user whether ticker selection is vald
    html.Hr(),
    html.Div(id='stock-data', style={'display': 'none'}), #intermediate storage of stock data dataframe.
    html.H5('FCFf drivers'),
    dash_table.DataTable(id='fcff-table', # Free cashflow table for selected stock
                         columns = [{"name": i, "id": i, 'format': Format(precision=2)} for i in (['Item'] + fcff_cols)],
                         data = ['FCFf'] + [0 for x in fcff_cols], editable = True),
    html.Hr(),
    html.H5('PV'),
    html.Div(dash_table.DataTable(id='valn-table', columns=[{"name": i, "id": i} for i in valuation_cols], data=[0 for x in ['Valuation']], editable = True)), # Valuation table, fed from FCff table
])

# Fill FCFf table with selected stock data
@ app.callback(
    [Output('fcff-table', 'columns'),
     Output('fcff-table', 'data')],
    [Input('stock-data', 'children')])
def display_output(df):
    stock_df = pd.read_json(df)
    fcff_table = pd.DataFrame(columns = fcff_cols, data = stock_df[fcff_cols].values.round(2), index=['FCFf'])
    fcff_table.reset_index(inplace=True)
    fcff_table.rename(columns={'index': 'Item'}, inplace=True)
    fcff_col_param = []
    for col in ['Item'] + fcff_cols:
        fcff_col_param.append({"name": str(col), "id": str(col)})
    return [fcff_col_param, fcff_table.to_dict('records')]


# Read data in from editable FaCFf table, and use to calculate and populate valn-table
@app.callback(
    [Output('valn-table', 'columns'),
     Output('valn-table', 'data')],
    [Input('fcff-table', 'data'),
     Input('stock-data', 'children')])
def display_valn(f_data, df):
    stock_df = pd.read_json(df) # Read current stock data from intermediate div in html.
    fcff_df = pd.DataFrame(f_data) # Convert FCFf table data to dataframe
    fcff_df = fcff_df[fcff_df.Item == 'FCFf'].drop('Item', axis=1)
    fcff_df.columns = fcff_cols

    stock_df[fcff_cols] = fcff_df[fcff_cols].values # overwrite fcff data with fcff data from the table, allowing users to change fcff assumptions.
    stock_df = do_pv(stock_df) # do PV of cashflows using stock's discount rate, r

    # construct valuation table output for display in valn-table
    data = [['Present Value of forecast FCFf (m)', stock_df['pv'].values[0].round(2)], ['Discount rate (r)', stock_df['r'].values[0].round(3)]],

table = pd.DataFrame(data, columns = ['Item', 'Valuation'])

    dt_col_param = [{"name": 'Item', "id": 'Item'}, {"name": 'Valuation', "id": 'Valuation'}]
    return [dt_col_param, table.to_dict('records')]

# Select stock for consideration via manual entry of ticker.
@app.callback(
    Output('my-tick', 'children'),
    [Input('stock-id', 'value')]
)
def update_output_ticker(input_value):
    if not input_value in df.ticker.unique():
        return 'Incorrect ticker'
    else:
        return df[df.ticker == input_value].ticker

# Store selected stock data dataframe in intermediate step.
@app.callback(Output('stock-data', 'children'),
              [Input('stock-id', 'value')])
def do_stock_df(selected_ticker):
    stock_df = df[df.ticker == selected_ticker]
    return stock_df.to_json()

def do_pv(df):
    df['pv'] = 0
    # pv of 5-year forecasts
    for i in range(1, 5):
        df.pv += df['fcff_fy' + str(i)].values[0] / ((1 + df.r.values[0]) ** i)
    return df

if __name__ == '__main__':
    app.run_server(debug=True)

尝试将 do_pv 模块更改为以下内容,看起来在 table 被编辑后,值被提取为导致此错误的字符串。

def do_pv(df):
    df['pv'] = 0
    # pv of 5-year forecasts
    for i in range(1, 5):
        #its string for the edited columns
        print(type(df['fcff_fy' + str(i)].values[0])) 
        df['pv'] +=  float(df['fcff_fy' + str(i)].values[0]) / float((1 + df.r.values[0]) ** i)
    return df