*! 1.1.0 Ariel Linden 10oct2025 // changed code to display xvar values rather than sequential numbering // fixed width of table columns *! 1.0.0 Ariel Linden 07oct2025 program define menger, rclass version 11 syntax varlist(min=2 max=2 numeric) [if] [in] [, GRaph DEtail] qui { * Parse variables xvar and yvar tokenize `varlist' local xvar `1' local yvar `2' * Mark the sample to use marksample touse * Sort data by xvar sort `xvar' * Initialize results tempvar curvature menger_results gen `curvature' = . * Calculate Menger curvature for each triplet of consecutive points count if `touse' local N = r(N) forvalues i = 2/`=`N'-1' { local i1 = `i' - 1 local i2 = `i' local i3 = `i' + 1 * Get the three points local x1 = `xvar'[`i1'] local y1 = `yvar'[`i1'] local x2 = `xvar'[`i2'] local y2 = `yvar'[`i2'] local x3 = `xvar'[`i3'] local y3 = `yvar'[`i3'] * Calculate area of triangle using determinant formula local area = 0.5 * abs((`x1'*(`y2'-`y3') + `x2'*(`y3'-`y1') + `x3'*(`y1'-`y2'))) * Calculate side lengths local a = sqrt((`x2'-`x3')^2 + (`y2'-`y3')^2) // side between points 2 and 3 local b = sqrt((`x1'-`x3')^2 + (`y1'-`y3')^2) // side between points 1 and 3 local c = sqrt((`x1'-`x2')^2 + (`y1'-`y2')^2) // side between points 1 and 2 * Calculate Menger curvature: 4 * area / (product of sides) if `a' * `b' * `c' > 0 { replace `curvature' = 4 * `area' / (`a' * `b' * `c') in `i' } } * create matrix of relevant values count if `curvature' != . local matrix_rows = r(N) matrix `menger_results' = J(`matrix_rows', 2, .) local row = 1 forvalues i = 2/`=`N'-1' { if `curvature'[`i'] != . { // Get the format of xvar local fmt : format `xvar' local fmt_clean = substr("`fmt'", 2, .) // Extract the value of xvar[`i`] formatted quietly { local xi : display %`fmt_clean' `xvar'[`i'] } local rname `rname' `xi' matrix `menger_results'[`row', 1] = `yvar'[`i'] matrix `menger_results'[`row', 2] = `curvature'[`i'] local row = `row' + 1 } } * matrix names matrix colnames `menger_results' = `yvar' curvature matrix rownames `menger_results' = `rname' * if detail is specified * if "`detail'" != "" { * get rspec() values local ylablen = strlen("`yvar'") local yvalen : format `yvar' local yvalen = substr(substr("`yvalen'", 2, .), 1, strpos(substr("`yvalen'", 2, .), ".") - 1) local ylen = max(`ylablen', `yvalen' ) local xlablen = strlen("`xvar'") local xvalen : format `xvar' local xvalen = substr(substr("`xvalen'", 2, .), 1, strpos(substr("`xvalen'", 2, .), ".") - 1) local xlen = max(`xlablen', `xvalen') local nrows = rowsof(`menger_results') local rspec_str "&-" forvalues i = 1/`=`nrows'-1' { local rspec_str "`rspec_str'&" } local rspec_str "`rspec_str'-" * Display the matrix noi di " " noi matlist `menger_results', rowtitle(`xvar') cspec(o4& %`xlen's | w`ylen' %8.5f & w9 %8.5f &) rspec("`rspec_str'") } // end detail * Find maximum curvature and corresponding xvar tempvar has_curvature gen `has_curvature' = `curvature' != . sum `curvature' if `has_curvature', meanonly local max_curvature = r(max) * Find the xvar value where maximum curvature occurs levelsof `xvar' if abs(`curvature' - `max_curvature') < 1e-10 & `has_curvature', local(elbow) * Save results return scalar max_curv = `max_curvature' return scalar elbow = `elbow' return matrix results = `menger_results' noi di " " noi di "Max curvature: " %8.5f `max_curvature' " at `xvar' = " %`fmt_clean' `elbow' } // end qui // Optional graph if "`graph'" != "" { * get var labels for graph local yl : variable label `yvar' if `"`yl'"' == "" local yl "`yvar'" local xl : variable label `xvar' if `"`xl'"' == "" local xl "`xvar'" twoway (connected `yvar' `xvar', lwidth(medthick) msymbol(o)) /// (scatter `yvar' `xvar' if `xvar'==`elbow', /// msymbol(O) mcolor(red) msize(large) mlabel(`xvar') mlabposition(2) mlabsize(medium)), /// title("Menger Curvature") /// xtitle(`xl') ytitle("`yl'") /// legend(off) } end