*! pystacked v0.8.0
*! last edited: 14july2026
*! authors: aa/ms
*! pystacked2_p = uses python code appended to the file

// do not edit this file manually; edit pystacked1_p.ado, then run R script
// parent program
program define pystacked2_p, rclass
    version 16.0
    syntax namelist(min=1 max=2) [if] [in], [ ///
                                                            pr ///
                                                            xb ///
                                                            Resid ///
                                                            class ///
                                                            TRANSForm /// legacy option, equivalent to basexb
                                                            BASExb ///
                                                            force ///
                                                            CValid ///
                                                            ]

    // python: from pystacked_p import *

    if ("`force'"=="") {
        // check datasignature based on depvar, xvars and foldvar
        fvrevar `e(depvar)' `e(allxvars_o)' `e(foldvar)', list
        local dslist `r(varlist)'
        local dslist : list uniq dslist
        qui _datasignature `dslist'
        if "`e(datasignature)'"~="`r(datasignature)'" {
            di as err "error: data in memory has changed since last -pystacked- call"
            di as err "you are not allowed to change data in memory between -pystacked- fit and -predict-"
            di as err "use the -force- option to override"
            exit 198
        }
        // check sort order
        local sortvars : sortedby
        if "`sortvars'"~="`e(sortvars)'" {
        	di as err "error: sort order of data in memory has changed since last -pystacked- call"
            di as err "you are not allowed to change data in memory between -pystacked- fit and -predict-"
            exit 198
        }
    }

    * legacy option
    if "`transform'"~="" {
        local basexb basexb
        local transform
        di as err "transform option is deprecated; use 'basexb'"
    }

    * only 1 option max
    local optcount : word count `resid' `pr' `xb' `class'
    if `optcount'>1 {
        di as err "only one of options 'pr xb resid class' allowed"
        exit 198
    }

    if "`resid'"!="" & "`basexb'`cvalid'"!="" {
        di as err "resid not allowed with: `basexb' `cvalid'"
        exit 198
    }

    if "`cvalid'"~="" & "`basexb'"=="" {
        di as err "error - option cvalid currently supported only with option basexb"
        exit 198
    }

    local command=e(cmd)
    if ("`command'"~="pystacked") {
        di as err "error: -pystacked_p- supports only the -pystacked- command"
        exit 198
    }
    *

    local depvar `e(depvar)'

    tokenize `namelist'
    if "`2'"=="" {                    //  only new varname provided
        local predictvar `1'
    }
    else {                            //  datatype also provided
        local vtype `1'
        local predictvar `2'
    }
    *

    marksample touse, novarlist

    if "`basexb'"=="" {
        qui gen `vtype' `predictvar' = .
    }

    * Get predictions
    python: post_prediction("`predictvar'","`basexb'","`cvalid'","`vtype'","`touse'","`pr'`xb'`class'")

    if "`resid'"!="" {
        replace `predictvar' = `depvar' - `predictvar' if `touse'
    }

end

* --- Python code appended below ---
python:
#! pystacked v0.8.0
#! last edited: 14july2026
#! authors: aa/ms

# Import SFI, always with stata 16
from sfi import Data,Matrix,Scalar,Macro
from sfi import SFIToolkit
import numpy as np

def post_prediction(pred_var,basexb,cvalid,var_type,touse,pred_type):

	# Start with a working flag
	Scalar.setValue("r(import_success)", 1, vtype='visible')

	# Import model from Python namespace
	try:
		from __main__ import model_object as model
		from __main__ import model_xvars as xvars
		from __main__ import model_methods as methods
		from __main__ import id as id
		from __main__ import type as type
	except ImportError:
		print("Error: Could not find pystacked estimation results.")
		Scalar.setValue("r(import_success)", 0, vtype='visible')
		return

	touse = np.array(Data.get(touse))

	if type=="class" and pred_type=="":
		pred_type="pr"
	elif type=="reg" and pred_type=="":
		pred_type="xb"
	elif type=="class" and pred_type=="xb":
		SFIToolkit.stata('di as err "xb/resid not supported with classification"')
		#"
		SFIToolkit.error(198)
	elif type=="reg" and pred_type=="class":
		SFIToolkit.stata('di as err "class not supported with regression"')
		#"
		SFIToolkit.error(198)
	elif type=="reg" and pred_type=="pr":
		SFIToolkit.stata('di as err "pr not supported with regression"')
		#"
		SFIToolkit.error(198)

	if basexb=="" and cvalid=="":
		# stacked prediction
		if type=="class" and pred_type == "pr":
			from __main__ import predict_proba as pred
			if pred.ndim>1:
				pred=pred[:,1]
		elif type=="class" and pred_type == "class":
			from __main__ import predict as pred
			if pred.ndim>1:
				pred=pred[:,1]
		else:
			from __main__ import predict as pred
		pred[touse==0] = np.nan
		Data.store(var=pred_var,val=pred,obs=None)

	elif basexb!="" and cvalid=="":
		# learner predictions
		from __main__ import transform as transf
		ncol = transf.shape[1]
		for j in range(ncol):
			if var_type == "double":
				Data.addVarDouble(pred_var+str(j+1))
			else:
				Data.addVarFloat(pred_var+str(j+1))
			pred=transf[:,j]
			predna =np.isnan(pred)
			if pred_type=="class":
				pred=(pred>0.5)*1
				pred=pred.astype(float)
				pred[predna]=np.nan
				Data.setVarLabel(pred_var+str(j+1),"Predicted class "+" "+methods[j])
			elif type=="class":
				Data.setVarLabel(pred_var+str(j+1),"Predicted probability "+" "+methods[j])
			else:
				Data.setVarLabel(pred_var+str(j+1),"Predicted value"+" "+methods[j])
			pred[touse==0] = np.nan
			Data.store(var=pred_var+str(j+1),val=pred,obs=None)

	elif basexb!="" and cvalid!="":
		try:
			from __main__ import cvalid as transf
			if np.isnan(transf).all():
				SFIToolkit.stata('di as res "Warning: cvalid option not available with selected final estimator;"')
				SFIToolkit.stata('di as res "         cross-validated predicted values are set to missing"')
		except ImportError:
			SFIToolkit.stata('di as err "Error: Could not find cross-validated predicted values."')
			#"
			SFIToolkit.error(198)
			return
		id = id -1
		id = id.tolist()
		ncol = transf.shape[1]
		touse =touse[id]
		for j in range(ncol):
			if var_type == "double":
				Data.addVarDouble(pred_var+str(j+1))
			else:
				Data.addVarFloat(pred_var+str(j+1))
			pred=transf[:,j]
			predna =np.isnan(pred)
			if pred_type=="class":
				pred=(pred>0.5)*1
				pred=pred.astype(float)
				pred[predna]=np.nan
				Data.setVarLabel(pred_var+str(j+1),"Cross-validated predicted class "+" "+methods[j])
			elif type=="class":
				Data.setVarLabel(pred_var+str(j+1),"Cross-validated predicted probability "+" "+methods[j])
			else:
				Data.setVarLabel(pred_var+str(j+1),"Cross-validated predicted value"+" "+methods[j])
			pred[touse==0] = np.nan
			Data.store(var=pred_var+str(j+1),val=pred,obs=id)
end