*! _gvar_avgcorr 1.0.1  21aug2026
*! gvar avgcorr -- average pairwise cross-section correlations of the data and
*! of the country-model residuals.
*! Author: Merwan Roudane
*! merwanroudane920@gmail.com
*! https://github.com/merwanroudane
*
* Step -> source map
*   rhobar_i = (sum_j rho_ij - 1)/(N-1) on levels, differences and residuals
*                                   <- Toolbox avgcorrs.m, corrmat.m
*   the same statistic in R         <- GVARX averageCORgvar
*   bucketed summary of |rhobar|    <- BGVAR avg.pair.cc
*
* Including the foreign variables in each country model should soak up the
* cross-section dependence, so the residual correlations ought to be much
* smaller than those of the data.  If they are not, generalized impulse
* responses are not trustworthy (Dees, di Mauro, Pesaran & Smith 2007).

program define _gvar_avgcorr, rclass
    version 14.0

    syntax [, BLOCK(string) noSUMmary GRaph NAME(string) SAVing(name) ]

    _gvar_require foreign

    if ("`block'" == "") local block "levels diff resid"
    local block = lower("`block'")

    mata: st_local("N",  strofreal(gvar_getN()))
    mata: st_local("cn", invtokens(gvar_getcname()'))
    mata: st_local("vn", invtokens(gvar_getvname()'))

    tempname R
    mata: st_matrix("`R'", gvar_avgcorr_all())
    local nr = rowsof(`R')

    local vl ""
    forvalues q = 1/`nr' {
        local j = `R'[`q', 1]
        local v : word `j' of `vn'
        local p : list posof "`v'" in vl
        if (`p' == 0) local vl "`vl' `v'"
    }
    local vl = trim("`vl'")
    local nv : word count `vl'

    if ("`summary'" != "nosummary") {
        foreach b of local block {
            if ("`b'" == "levels") {
                local col 3
                local blab "Levels of the variables"
            }
            if ("`b'" == "diff") {
                local col 4
                local blab "First differences of the variables"
            }
            if ("`b'" == "resid") {
                local col 5
                local blab "VECMX* residuals"
            }
            _gvar_avgcorr_table `R' `nr' `col' "`blab'" "`cn'" "`vl'" "`vn'"
        }
    }

    if ("`graph'" != "") {
        _gvar_avgcorr_graph `R' `nr' "`cn'" "`vl'" "`vn'" "`name'"
    }

    if ("`saving'" != "") {
        matrix `saving' = `R'
    }
    return matrix avgcorr = `R', copy
end

* ---------------------------------------------------------------------------
program define _gvar_avgcorr_table
    version 14.0
    args R nr col blab cn vl vn

    local nv : word count `vl'
    local nu : word count `cn'
    local w = 14 + 9 * `nv'
    if (`w' > 120) local w 120

    di ""
    di as text "{hline `w'}"
    di as text "  {bf:Average pairwise cross-section correlations} -- `blab'"
    di as text "{hline `w'}"
    di as text %-13s "  Unit" _continue
    foreach v of local vl {
        di as text %9s abbrev("`v'", 8) _continue
    }
    di ""
    di as text "{hline `w'}"

    * bucket counters, as in BGVAR's avg.pair.cc
    local b1 0
    local b2 0
    local b3 0
    local b4 0
    forvalues i = 1/`nu' {
        local u : word `i' of `cn'
        local any 0
        forvalues q = 1/`nr' {
            if (`R'[`q', 2] == `i' & `R'[`q', `col'] < .) local any 1
        }
        if (`any' == 0) continue
        di as text "  " %-11s abbrev("`u'", 11) _continue
        foreach v of local vl {
            local jj : list posof "`v'" in vn
            local x .
            forvalues q = 1/`nr' {
                if (`R'[`q',1] == `jj' & `R'[`q',2] == `i') {
                    local x = `R'[`q', `col']
                    continue, break
                }
            }
            if (`x' >= .) {
                * A dash, not a dot: this unit does not have this variable, so
                * there is no series to correlate.  Eleven countries have no long
                * rate and seven no equity price, which is most of the blanks in
                * this table; the usa has no exchange rate because it is the
                * numeraire.  A dot would suggest a correlation that failed to
                * compute, and none do.
                di as text %9s "-" _continue
            }
            else {
                di as result %9.3f `x' _continue
                local a = abs(`x')
                if (`a' <= 0.1)                    local ++b1
                else if (`a' > 0.1 & `a' <= 0.2)   local ++b2
                else if (`a' > 0.2 & `a' <= 0.5)   local ++b3
                else                               local ++b4
            }
        }
        di ""
    }
    di as text "{hline `w'}"
    local tot = `b1' + `b2' + `b3' + `b4'
    if (`tot' > 0) {
        * State what the percentages are taken over.  The shares are of the
        * cells that EXIST, so a reader comparing `tot' with 26 x (number of
        * variables) will find it short -- by exactly the dashes, not by
        * anything lost.
        di as text "  {bf:-} means that unit does not have that variable."
        di as text "  Shares below are over the " as result `tot' ///
           as text " (unit, variable) pairs that exist."
        di as text "  |rho| distribution:  " ///
           as text "<=0.1 " as result `b1' as text " (" %4.1f `=100*`b1'/`tot'' "%)  " ///
           as text "0.1-0.2 " as result `b2' as text " (" %4.1f `=100*`b2'/`tot'' "%)  " ///
           as text "0.2-0.5 " as result `b3' as text " (" %4.1f `=100*`b3'/`tot'' "%)  " ///
           as text ">0.5 " as result `b4' as text " (" %4.1f `=100*`b4'/`tot'' "%)"
    }
end

* ---------------------------------------------------------------------------
* Levels vs residual correlations: the residuals should collapse toward zero
* ---------------------------------------------------------------------------
program define _gvar_avgcorr_graph
    version 14.0
    args R nr cn vl vn gname
    if ("`gname'" == "") local gname gvar_avgcorr

    _gvar_palette
    local reg  "`r(region)'"
    local c1   "`r(c1)'"
    local c2   "`r(c2)'"
    local zero "`r(zero)'"

    preserve
    clear
    qui set obs `nr'
    qui gen int    vj  = .
    qui gen int    ui  = .
    qui gen double lev = .
    qui gen double res = .
    forvalues q = 1/`nr' {
        qui replace vj  = `R'[`q', 1] in `q'
        qui replace ui  = `R'[`q', 2] in `q'
        qui replace lev = `R'[`q', 3] in `q'
        qui replace res = `R'[`q', 5] in `q'
    }
    qui drop if missing(lev) & missing(res)

    twoway ///
        (function y = x, range(-1 1) lcolor("`c2'") ///
             lpattern(shortdash) lwidth(medthin)) ///
        (scatter res lev, msymbol(circle) msize(small) ///
             mcolor("`c1'%60") mlcolor("`c1'") mlwidth(vthin)) ///
        , `reg' ///
          yline(0, lcolor("`zero'") lpattern(dash)) ///
          xline(0, lcolor("`zero'") lpattern(dash)) ///
          ylabel(-1(0.5)1, angle(0) labsize(small) grid glcolor(gs15)) ///
          xlabel(-1(0.5)1, labsize(small)) ///
          ytitle("average correlation of the residuals", size(small)) ///
          xtitle("average correlation of the levels", size(small)) ///
          title("Cross-section dependence before and after conditioning", ///
                size(medium) color(black)) ///
          subtitle("points should fall well inside the 45-degree line", ///
                   size(vsmall) color(gs7)) ///
          aspectratio(1) name(`gname', replace) legend(off)
    restore
end