/*
User-written Stata ado
Estimates linear Fixed-Effects model with Individual-specific Slopes (FEIS)
Author: Volker Ludwig
Version 1.0: 23-07-2015
Version 1.1: 17-07-2018
- Fix compatibility of clustered s.e. with Stata 15
Version 2.0: 16-04-2019
- new option addsp() to add estimated individual slope parameters to the data
*/

mata:
function _feis_est(string scalar id, string scalar av, string scalar uv, string scalar slope, string scalar touse, string scalar noconstant, string scalar c1_, string scalar cluster, string scalar sp, string scalar transformed, string scalar addsp)
{


/* get data matrix */
ID=st_data(., id, touse)
Y=st_data(., av, touse)
X=st_data(., tokens(uv), touse)

if (strlen(slope)>0) {
        Z=st_data(., tokens(slope), touse)
        if (strlen(noconstant)==0) {
                real colvector c
                c=J(rows(Z),1,1)
                Z=(Z,c)
        }
}
else if (strlen(slope)==0 & strlen(noconstant)==0) {
        Z=J(rows(Y),1,1)
}

/* set up panel data info matrix (identifies submatrix of units i) */
info = panelsetup(ID, 1, cols(Z)+1)

/* transform data matrix (premultiply by m),
store transformed dep.var. in AV, transformed indep.vars in UV */
real colvector AV
real matrix UV
UV=J(0,cols(X),.)
real matrix m
i=1
while (i<=rows(info)) {
    panelsubview(z, Z, i, info)
    panelsubview(x, X, i, info)
    panelsubview(y, Y, i, info)
    /*residual maker*/
	m=I(rows(z))-z*invsym(cross(z, z))*z'
    i++
    /*within transformation*/
	y=m*y
    AV=(AV\y)
    x=m*x
    UV=(UV\x)
}

/* add transformed data to current data set */
if (strlen(transformed)>0) {
    name = transformed + av
    st_store(., st_addvar("double", name), touse, AV)
    xvars = J(1,cols(UV),tokens(uv))
    i=1
    while (i<=cols(UV)) {
		name = transformed + xvars[i]
        st_store(., st_addvar("double", name), touse, UV[.,i])
        i++
    }
}

/* compute coefficient vector */
real colvector b
b=invsym(cross(UV, UV))*UV'*AV

/* compute residual vector */
real colvector u
u=AV-(UV*b)

/* compute variance matrix */
real matrix v
real scalar mindf
real scalar sigma
if (strlen(cluster)==0) {
    mindf=rows(info)*cols(Z)+cols(X)-1
    sigma=sum(u:*u)/(rows(u)-mindf)
    v=sigma*invsym(cross(UV, UV))
}
if (strlen(cluster)>0) {
    mindf=cols(Z)+cols(X)-1
    v=invsym(cross(UV, UV))
	/* store to tempvar c1 for _robust routine */
	st_view(c1=., ., c1_, touse)
	c1[.,.]=u
}

/* compute R2 within */
real scalar rss
rss=sum(u:*u)
real scalar tss
tss=sum((AV):*(AV))
real scalar r2
r2=1-(rss/tss)

/* compute APE and variance for ind. constants and slopes */
if (strlen(sp)>0) {
    real colvector alpha
	alpha=J(cols(Z),1,0)
	real colvector s
	real matrix svar
	svar=J(0,cols(Z),.)
	real matrix S
	S=J(cols(Z),cols(Z),0)
	real matrix C
	C=invsym(cross(Z, Z))*Z'*X
	real matrix A
	A=cross(UV, UV)
	real colvector B
	B=C*invsym(A)*UV'*u
	/* ind. constants and slopes */
	i=1
	while (i<=rows(info)) {
		panelsubview(z, Z, i, info)
		panelsubview(x, X, i, info)
		panelsubview(y, Y, i, info)
		s=invsym(cross(z, z))*z'*(y-x*b)
		alpha=(alpha:+s)
		if (strlen(addsp)>0) {
			svar=(svar\s')
		}
		i++
	}
	alpha=alpha:/(rows(info))
	/* variance matrix */
	i=1
	while (i<=rows(info)) {
		panelsubview(z, Z, i, info)
		panelsubview(x, X, i, info)
		panelsubview(y, Y, i, info)
		s=invsym(cross(z, z))*z'*(y-x*b)
		s=((s-alpha)-B)*(((s-alpha)-B)')
		S=(S:+s)
		i++
	}
	S=S:/(rows(u)-(cols(Z)+cols(X)))
}

/* add estimated individual slopes to current data set*/
if (strlen(addsp)>0) {
	real matrix SV
	SV=J(rows(u),cols(Z),.)
	i=1
	while (i<=rows(info)) {
		j=info[i,1]
		while (j<=info[i,2]) {
			SV[j,.]=svar[i,.]
			j++
		}
		i++
	}
	svars = J(1,1,tokens(slope))
	if (strlen(noconstant)==0) {
		svars=svars,"cons"
	}
	j=1
	while (j<=cols(svars)) {
		name = addsp + svars[j]
        st_store(., st_addvar("double", name), touse, SV[.,j])
        j++
	}
}

/* pass results on to stata */
st_matrix("b", b')
st_matrix("V", v)
st_numscalar("mindf", mindf)
st_numscalar("N_g", rows(info))
st_numscalar("N_obs", rows(u))
info=info[.,2]-info[.,1]
st_numscalar("T_min", colmin(info)+1)
st_numscalar("T_max", colmax(info)+1)
st_numscalar("T_avg", sum(info)/rows(info)+1)
if (strlen(sp)>0) {
    st_matrix("a", alpha')
	st_matrix("sv", S)
}
if (strlen(addsp)>0) {
	st_matrix("SVAR", SV)
	st_matrix("svar", svar)
}
st_numscalar("R2", r2)

}
end


program define xtfeis, eclass
version 12
syntax varlist (min=2 numeric fv ts) [if] [in], [SLope(varlist numeric fv ts)] [NOConstant] [i(varname numeric)] [t(varname numeric)] [cluster(varname)] [sp] [TRANSformed(string)] [ADDsp(string)]

* Check group variable i() and time variable t()
if length("`i'") == 0 {
        local i = "`_dta[iis]'"
        if length("`i'") == 0 {
                di in red "you must specify a group variable to identify panels"
                di in red "use option -i()- or command -xtset-"
                exit 198
        }
}
if length("`t'") == 0 {
        local t = "`_dta[tis]'"
        if length("`t'") == 0 {
                di in red "you must specify a time variable"
                di in red "use option -t()- or command -xtset-"
                exit 198
        }
}

* Check groups are nested within Clusters
tempvar ch
if length("`cluster'") > 0 {
        qbys `i' (`t') : ge `ch' = `cluster'!=`cluster'[_n-1] & _n>1
        qui su `ch'
        if r(mean)>0 {
                di in red "group variable i must be nested within clusters"
				exit 198
        }
}

* Check at least slope or constant
if length("`noconstant'")>0 & length("`slope'")==0 {
        di in red "you must specify a slope variable or allow for individual constant"
        exit 198
}

* Expand macros
marksample touse
fvunab varlist : `varlist'
if length("`slope'")>0 {
        fvunab slope : `slope'
}
unab i : `i'
markout `touse' `slope' `i' `cluster'
tempvar no
qbys `i' : gen byte `no'=sum(`touse')
local s : word count `slope'
local s=`s'+1
if length("`noconstant'")==0 {
        local s=`s'+1
}
qbys `i' : replace `no'=. if `no'[_N]<`s'
qbys `i' : replace `no'=1 if `no'[_N]>=`s' & `no'[_N]<.
markout `touse' `slope' `i' `cluster' `no'

tokenize `varlist'
local av `1'
macro shift
local uv `*'
local id `i'
tempvar c1
qui ge `c1'=.


* invoke mata function
mata: _feis_est("`id'", "`av'", "`uv'", "`slope'", "`touse'", "`noconstant'", "`c1'", "`cluster'", "`sp'", "`transformed'", "`addsp'")


* compute panel-robust s.e.
qui reg `av' `uv' if `touse', nocons
mat beta=e(b)
local uvnames : colfullnames e(b)
mat beta=b
mat colnames beta = `uvnames'
mat Var=e(V)
local mindf=mindf
local df_r=N_obs-`mindf'
if length("`cluster'")>0 {
        mat rownames V = `uvnames'
        mat colnames V = `uvnames'
		_robust `c1' if `touse', v(V) cluster(`cluster') minus(`mindf')
        local df_r=`r(df_r)'
}
mat Var=V
mat rownames Var = `uvnames'
mat colnames Var = `uvnames'
ereturn post beta Var, esample(`touse') depname(`av') dof(`df_r') findomitted buildfvinfo
ereturn local ivar "`i'"
ereturn scalar N=N_obs
ereturn scalar N_g=N_g
ereturn scalar g_min=T_min
ereturn scalar g_avg=T_avg
ereturn scalar g_max=T_max
ereturn scalar r2_w=R2
ereturn local cmd "xtfeis"
ereturn local cmdline "xtfeis `0'"
ereturn local depvar "`av'"
ereturn local indepvar "`uv'"
ereturn local slopevar "`slope'"
ereturn local vcetype "conventional"
if length("`cluster'")>0 {
	ereturn local vcetype "cluster"
}
ereturn local noconstant ""
if length("`noconstant'")>0 {
	ereturn local noconstant "1"
}


* Table regression results
#delimit ;
di _n in gr "Fixed-effects regression with individual-specific slopes (FEIS)" _n ;
        di in gr "Group variable: " in ye abbrev("`e(ivar)'",12) in gr
                   _col(49) in gr "Number of obs" _col(68) "="
                _col(70) in ye %9.0f e(N) ;
                di in gr _col(49) "Number of groups" _col(68) "="
                _col(70) in ye %9.0g e(N_g) _n ;
        di in gr "R-sq:  within  = " in ye %6.4f e(r2_w)
                _col(49) in gr "Obs per group: min" _col(68) "="
                _col(70) in ye %9.0g e(g_min) ;
        di in gr
                _col(64) in gr "avg" _col(68) "="
                _col(70) in ye %9.1f e(g_avg) ;
        di in gr
                _col(64) in gr "max" _col(68) "="
                _col(70) in ye %9.0g e(g_max) _n ;

if length("`cluster'")>0 {;
        di _n in gr "Standard errors adjusted for clusters in " in gr "`cluster'" _n ;
};
#delimit cr
ereturn display, noempty

* Table of estimated Slope Parameters (Average Partial Effects)
if length("`sp'")>0 {
	di _n in gr "Estimated slope parameters (Average Partial Effects)" _n
	qui reg `av' `slope'  if e(sample), `noconstant'
	mat sp=e(b)
	local spnames : colfullnames e(b)
	mat sp=a
	mat spv=e(V)
	mat spv=sv
	mat colnames sp = `spnames'
	mat colnames spv = `spnames'
	mat rownames spv = `spnames'
	ereturn post sp spv, findomitted buildfvinfo
	ereturn display, noempty
}

end