<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>The DO Loop</title>
	<atom:link href="https://blogs.sas.com/content/iml/feed" rel="self" type="application/rss+xml" />
	<link>https://blogs.sas.com/content/iml/</link>
	<description>Statistical programming in SAS with an emphasis on SAS/IML programs</description>
	<lastBuildDate>Sat, 19 Sep 2026 15:52:26 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6</generator>
	<item>
		<title>How to compare two independent Monte Carlo estimates</title>
		<link>https://blogs.sas.com/content/iml/2026/09/21/diff-mc-estimates.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/09/21/diff-mc-estimates.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 21 Sep 2026 09:30:23 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Simulation]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=60192</guid>

					<description><![CDATA[<p>I write a lot of Monte Carlo simulations. This article discusses how to assess whether two independent Monte Carlo estimates are close to each other. This result can be used to test the correctness of a Monte Carlo simulation. It can also be used to compare different Monte Carlo algorithms [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/21/diff-mc-estimates.html">How to compare two independent Monte Carlo estimates</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
I write a lot of Monte Carlo simulations. This article discusses how to assess whether two independent Monte Carlo estimates are close to each other. This result can be used to test the correctness of a Monte Carlo simulation. It can also be used to <a href="https://blogs.sas.com/content/iml/2016/03/14/monte-carlo-estimates-of-pi.html">compare different Monte Carlo algorithms that compute the same quantity</a>.
</p>

<img fetchpriority="high" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MCDiff1-1.png" alt="" width="397" height="200" class="alignright size-full wp-image-60234" srcset="https://blogs.sas.com/content/iml/files/2026/09/MCDiff1-1.png 397w, https://blogs.sas.com/content/iml/files/2026/09/MCDiff1-1-300x151.png 300w, https://blogs.sas.com/content/iml/files/2026/09/MCDiff1-1-164x82.png 164w" sizes="(max-width: 397px) 100vw, 397px" />

<p>
A Monte Carlo (MC) simulation provides both a statistical estimate for a parameter and a confidence interval for the parameter. (The confidence interval assumes that the estimate follows a normal distribution.)
If you run two independent MC simulations to estimate the same parameter, how close will the two estimates be? This is illustrated by the diagram to the right. There are two MC estimates (<em>q<sub>1</sub></em> and <em>q<sub>2</sub></em>) and two confidence intervals for an unknown parameter, &mu;. 
How close are the estimates to each other? Intuitively, you might expect to bound the distance between the estimates by the width of the larger confidence interval, but is there a better way to compare the distance between two estimates? 
</p><p>
Yes, there is a better way. You can compute the variance of the difference between the two estimates
and construct a 95% confidence interval for that difference.
This article shows how to perform the computation in SAS by using the SAS IML language.
We will also verify the empirical 95% coverage for an example.
</p>


<h3>Generate independent Monte Carlo estimates</h3>
<p>
To make the ideas concrete, let's look at a specific problem.
Suppose you want to estimate 
the expected value of the Exponential distribution with scale parameter 10.
Call that quantity &mu;.
You can prove analytically that &mu; = 10 is the true expected value.  
</p><p>
The following SAS IML function performs a Monte Carlo simulation to estimate &mu;. 
The function generates <em>B</em> independent samples of size <em>N</em> from the Expo(10) distribution. 
It calculates the mean of each sample. The 
Monte Carlo estimate is the average of the estimates.
The Monte Carlo standard error is the standard deviation of the estimates divided by sqrt(B).
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* use Monte Carlo simulation to estimate the mean of the
   Expo(lambda) distribution. The simulation uses B independent
   samples of size N. Each sample is independently drawn from Expo(lambda) */</span>
start MC_Est_Expo<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, lambda<span style="color: #66cc66;">&#41;</span>;
    <span style="color: #0000ff;">x</span> = j<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, .<span style="color: #66cc66;">&#41;</span>;
    <span style="color: #0000ff;">call</span> randgen<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, <span style="color: #a020f0;">&quot;Expo&quot;</span>, lambda<span style="color: #66cc66;">&#41;</span>;
    sample_means = <span style="color: #0000ff;">mean</span><span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;                <span style="color: #006400; font-style: italic;">/* mean of each column */</span>
    MC_est = <span style="color: #0000ff;">mean</span><span style="color: #66cc66;">&#40;</span>sample_means`<span style="color: #66cc66;">&#41;</span>;          <span style="color: #006400; font-style: italic;">/* MC estimate of statistic */</span>
    SE_est = <span style="color: #0000ff;">std</span><span style="color: #66cc66;">&#40;</span>sample_means`<span style="color: #66cc66;">&#41;</span> / <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span>B<span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* MC standard error */</span>
    <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> MC_est || SE_est <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #0000ff;">N</span> = <span style="color: #2e8b57; font-weight: bold;">100</span>;
B = <span style="color: #2e8b57; font-weight: bold;">5000</span>;
lambda = <span style="color: #2e8b57; font-weight: bold;">10</span>;
<span style="color: #0000ff;">call</span> randseed<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">79</span><span style="color: #66cc66;">&#41;</span>;
est1 = MC_Est_Expo<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, Lambda<span style="color: #66cc66;">&#41;</span>;
est2 = MC_Est_Expo<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, Lambda<span style="color: #66cc66;">&#41;</span>;
print est1<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'MCest1'</span> <span style="color: #a020f0;">'SE1'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span>, est2<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'MCest2'</span> <span style="color: #a020f0;">'SE2'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MCDiff2.png" alt="" width="154" height="189" class="alignnone size-full wp-image-60204" />

<p>
Because the algorithm relies on random number simulation, every call results in a slightly different point estimate and standard error. Here, the estimate from the first simulation is larger than the true parameter (10); the other estimate is smaller. The standard errors are very similar: approximately 0.142.
</p>

<h3>The variance of the difference</h3>
<p>
Let <em>q<sub>1</sub></em> and <em>q<sub>2</sub></em> be two independent MC estimates, with standard errors <em>SE<sub>1</sub></em> and <em>SE<sub>2</sub></em>. Since both estimates are computed by the same method, the expected value of their difference is zero: E[<em>q<sub>1</sub></em> - <em>q<sub>2</sub></em>] = 0. (If we used different methods, and those methods are biased by different amounts, the expected value will not be zero.)
</p>
<p>
How much variance should we expect for the difference? Because <em>q<sub>1</sub></em> and <em>q<sub>2</sub></em> are independent, the variance of their difference is the sum of the individual variances:
<br />
Var(<em>q<sub>1</sub></em> - <em>q<sub>2</sub></em>) = <em>SE<sub>1</sub></em><sup>2</sup> + <em>SE<sub>2</sub></em><sup>2</sup>
</p>
<p>
Let <em>SE<sub>diff</sub></em> be the square root of this sum. For this example,
both estimates are normally distributed, so the difference also follows a normal distribution: <em>q<sub>1</sub></em> - <em>q<sub>2</sub></em> &sim; N(0, <em>SE<sub>diff</sub></em>). 
You can use this distribution to construct a 95% interval for the difference. 
Let's do that, then check whether the distance between these estimates 
|<em>q<sub>1</sub></em> - <em>q<sub>2</sub></em>| is less than the width of the interval.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* given (q1, SE1) and (q2, SE2), what is a confidence interval for the 
   difference q1-q2? The expected value E[q1-q2] = 0, but what is the variance? */</span>
q1 = est1<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>;  q2 = est2<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>; 
SE1 = est1<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>; SE2 = est2<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>;
<span style="color: #006400; font-style: italic;">/* Standard deviation of the DIFFERENCE between two independent estimates */</span>
SE_diff = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span>SE1<span style="color: #006400; font-style: italic;">**2 + SE2**2);</span>
print SE1 SE2 SE_diff;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Can we bound |q1 - q2| with high probability? 
   Use a prediction interval based on the fact that 
   q1-q2 ~ N(0, SE_diff) */</span>
w95 = quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Normal&quot;</span>, <span style="color: #2e8b57; font-weight: bold;">0.975</span><span style="color: #66cc66;">&#41;</span> <span style="color: #006400; font-style: italic;">* SE_diff;</span>
<span style="color: #0000ff;">if</span> <span style="color: #0000ff;">abs</span><span style="color: #66cc66;">&#40;</span>q1 - q2<span style="color: #66cc66;">&#41;</span> &lt; w95 <span style="color: #0000ff;">then</span> 
    print <span style="color: #a020f0;">&quot;The difference |q1 - q2| is inside a 95% confidence interval&quot;</span>;
<span style="color: #0000ff;">else</span>
    print <span style="color: #a020f0;">&quot;The difference |q1 - q2| is NOT inside a 95% confidence interval&quot;</span>;</pre></td></tr></table></div>




<img decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MCDiff3.png" alt="" width="356" height="108" class="alignnone size-full wp-image-60213" srcset="https://blogs.sas.com/content/iml/files/2026/09/MCDiff3.png 356w, https://blogs.sas.com/content/iml/files/2026/09/MCDiff3-300x91.png 300w" sizes="(max-width: 356px) 100vw, 356px" />

<p>
For these estimates, the difference is, indeed, less than the width of a 95% confidence interval.
Of course, we might have gotten lucky. 
The next section investigates what happens if we repeat this experiment many times.
</p>

<h3>Verify the empirical coverage</h3>
<p>
According to probability theory, the difference between the two estimates should fall inside the calculated interval 95% of the time. However, I always like to verify statistical theory by running a simulation in SAS.
</p>
<p>
The following program repeats the experiment 1,000 times. For each iteration, the program generates two independent Monte Carlo estimates, computes the 95% confidence interval for their difference, and 
saves a 0/1 binary value that indicates if the difference falls inside the CI. 
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* If we repeat this computation many times, about 95% of the differences
   should be in the 95% CI */</span>
C = <span style="color: #2e8b57; font-weight: bold;">1000</span>;
crit_val = quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Normal&quot;</span>, <span style="color: #2e8b57; font-weight: bold;">0.975</span><span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* ~ 1.96 */</span>
inCI95 = j<span style="color: #66cc66;">&#40;</span>C, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">do</span> i=<span style="color: #2e8b57; font-weight: bold;">1</span> to C;
    est1 = MC_Est_Expo<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, Lambda<span style="color: #66cc66;">&#41;</span>;
    est2 = MC_Est_Expo<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, B, Lambda<span style="color: #66cc66;">&#41;</span>;
    q1 = est1<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>;  q2 = est2<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>; 
    SE1 = est1<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>; SE2 = est2<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>;
    SE_diff = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>SE1<span style="color: #006400; font-style: italic;">**2 + SE2**2) );</span>
    w95 = crit_val <span style="color: #006400; font-style: italic;">* SE_diff;</span>
    inCI95<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">abs</span><span style="color: #66cc66;">&#40;</span>q1 - q2<span style="color: #66cc66;">&#41;</span> &lt; w95<span style="color: #66cc66;">&#41;</span>; 
<span style="color: #0000ff;">end</span>; 
&nbsp;
propInCI = <span style="color: #0000ff;">mean</span><span style="color: #66cc66;">&#40;</span>inCI95<span style="color: #66cc66;">&#41;</span>;
print propInCI<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'Empirical 95% Cov'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MCDiff4.png" alt="" width="111" height="105" class="alignnone size-full wp-image-60216" />

<p>
Ah, success! 
The empirical coverage for this random number seed is close to the theoretical coverage of 0.95. This confirms that adding the individual variances provides a statistical interval for comparing the distance between two 
Monte Carlo estimates of the same quantity. 
</p>


<h3>Modifying this technique</h3>
<p>
You can change the critical value if you want to test the difference with more confidence. For example, you can use three standard errors to obtain a 99.7% confidence interval, which is known as the "three-sigma rule."
</p><p>
You can also compare the distance between two estimates that come from different (unbiased) estimates. For example, if one estimate is computed by using B<sub>1</sub>=5,000 random samples and the other is computed by using B<sub>2</sub>=3,000 random samples, the standard error for the second estimate will be larger, but you can still compute the standard error of the difference.
</p><p>
If you are testing a routine that produces a random or quasi-random result, you can use this technique to compare the routine's result to a MC validation procedure that you wrote yourself. Suppose the routine returns the value <em>q<sub>1</sub></em>. Your validation procedure gives the value <em>q<sub>2</sub></em>.
95% of the time,  <em>q<sub>1</sub></em> will be close to  <em>q<sub>2</sub></em>.
But there is a 5% chance that they are not sufficiently close. 
Does that indicate that the estimates are wrong? No, most likely you got unlucky.
One option is to rerun the routine and your verification procedure again. This gives new estimates
<em>q<sub>3</sub></em> and <em>q<sub>4</sub></em>. These new results also have a 5% chance of failure, but the probability that BOTH tests fail sequentially is a very small 0.25%.
If that probability is not small enough for you, you can use 99.7% CIs instead of 95% CIs.
</p>


<h3>Summary</h3>
<p>
This article shows a technique for comparing the difference between two Monte Carlo estimates of the same 
quantity. You can compute the variance of the difference as the sum of the two individual variances.
In many cases, the expected value of the difference is zero, and the difference is normally distributed. (You should check this assumption.) Therefore, you can derive a formula for the magnitude of the difference.
You can use this formula to verify the consistency of a single Monte Carlo simulation, or you can use it to verify that two different MC algorithms provide consistent estimates of the same quantity.
</p>
  <p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/21/diff-mc-estimates.html">How to compare two independent Monte Carlo estimates</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/09/21/diff-mc-estimates.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/09/MCDiff1-150x150.png" />
	</item>
		<item>
		<title>Implement a zero-inflated distribution in SAS</title>
		<link>https://blogs.sas.com/content/iml/2026/09/14/zero-inflated-beta-sas.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/09/14/zero-inflated-beta-sas.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 14 Sep 2026 09:22:32 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=60144</guid>

					<description><![CDATA[<p>SAS has more than 25 common probability distributions that are supported in the PDF, CDF, QUANTILE, and RAND functions. If you want to work with a less common distribution, you can implement these functions yourself. For example, I previously showed how to use PROC FCMP in Base SAS to implement [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/14/zero-inflated-beta-sas.html">Implement a zero-inflated distribution in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
SAS has more than 25 common probability distributions that are supported in the PDF, CDF, QUANTILE, and RAND functions. If you want 
to work with a less common distribution, you can implement these functions yourself. For example, I previously showed how to use PROC FCMP in Base SAS to implement <a href="https://blogs.sas.com/content/iml/2026/01/20/burr-sas.html">the Burr (Type XII) distribution</a> and <a href="https://blogs.sas.com/content/iml/2025/08/25/gev-sas.html">the generalized extreme value distribution</a>.
</p>

<a href="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta1.png" alt="" width="480" height="360" class="alignright size-full wp-image-60168" srcset="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta1.png 640w, https://blogs.sas.com/content/iml/files/2026/09/ZIBeta1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
A SAS programmer recently asked me questions about a zero-inflated (ZI) probability distribution. 
Some readers might be familiar with distributions that are related to zero-inflated <em>counts</em>.
These distributions are used in some linear regression models.
A zero-inflated model is a mixture of two distributions: a discrete probability mass situated at X=0, and a second probability distribution, 
which describes the probability that a random variable is not zero.
In regression, two common zero-inflated distributions are the ZI Poisson (ZIP) and the ZI negative binomial (ZINB) distributions. 
</p><p>
However, the SAS programmer wanted to implement a mixture distribution that is the mixture of a 
discrete point-mass distribution and a continuous distribution. The discrete distribution gives the probability that X=0. The continuous distribution gives the probability that X &gt; 0.
</p><p>
This article shows how to use SAS to create functions for the zero-inflated Beta distribution. The image to the right shows the PDF for a ZI Beta distribution.
The techniques in this article can be generalized to other mixture distributions, such as a ZI gamma distribution.
This article implements the functions in PROC FCMP and calls them from the DATA step. 
</p>

<h3>What is the zero-inflated Beta distribution?</h3>

<p>
<a href="https://en.wikipedia.org/wiki/Beta_distribution">The Beta distribution is a two-parameter distribution on the interval (0,1)</a>. The two positive shape parameters 
are often called "alpha" and "beta." To reduce confusion, I capitalize the name of the distribution and lowercase the name of the parameter.
Depending on the relative magnitudes of the shape parameters, the probability density function can be U-shaped, J-shaped, L-shaped, or mound-shaped (sometimes called "inverted-U shaped"). 
</p>
<p>
The equation for the probability density function (PDF) of a random variable that follows a zero-inflated distribution is 
<br />
&nbsp;&nbsp;&nbsp;&nbsp; 
f(x) = &pi; &delta;(x) + (1-&pi;) f<sub>2</sub>(x; &alpha;, &beta;) I(x &gt; 0)
<br />
where &delta;(x) is the Dirac delta function, &pi; is the mixing probability, f<sub>2</sub> is the probability density function of the second distribution, and I is the indicator function (also called the Heaviside function). For this article, f<sub>2</sub> is the PDF of a Beta distribution.
</p><p>
This article uses the values &alpha; = 2 and &beta; = 3 for the shape parameters.
For the probability of mixing, this article uses &pi; = 0.2. In programs, I use the variable p0 instead of &pi; for the mixing probability so that the parameter will not be confused with the mathematical constant 3.14159....
</p>


<h3>Define the zero-inflated functions in PROC FCMP</h3>
<p>
This article defines the four main functions that statistical programmers need: The PDF, CDF, quantile, and random functions
for the ZI Beta distribution.
They are defined in a PROC FCMP library and can be called from the SAS DATA step.
To simplify the article, I've put the PROC FCMP code in the Appendix. 
To reproduce the images in this article, you must run the PROC FCMP code <em>before</em> running the examples! 
I have stored the definitions in the WORK libref, but you replace WORK with a libref to a permanent location if you want to 
persist the definitions. If you do, be sure to specify the CMPLIB= option in each new session of SAS. The CMPLIB= option tells SAS where 
you stored the functions.
</p>

<h3>Parameter values for the examples</h3>
<p>
To keep this article short, all examples use the same parameter values for the Beta distribution. 
The %DefineParms macro defines three parameter values. It sets the mixing probability (p0) to 0.2.
It sets the Beta parameters to &alpha; = 2 and &beta; = 3.  You can modify the macro to explore other ZI Beta shapes.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #0000ff;">%macro</span> DefineParms;
   p0    = <span style="color: #2e8b57; font-weight: bold;">0.2</span>;
   alpha = <span style="color: #2e8b57; font-weight: bold;">2</span>;
   beta  = <span style="color: #2e8b57; font-weight: bold;">3</span>;
   <span style="color: #0000ff;">length</span> Params $40;
   Params = cats<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;alpha=&quot;</span>,<span style="color: #0000ff;">put</span><span style="color: #66cc66;">&#40;</span>alpha,BEST3.<span style="color: #66cc66;">&#41;</span>, <span style="color: #a020f0;">&quot;, beta=&quot;</span>, <span style="color: #0000ff;">put</span><span style="color: #66cc66;">&#40;</span>beta,BEST3.<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">%mend</span>;</pre></td></tr></table></div>





<h3>The ZI Beta PDF</h3>
<p>
When I study a new distribution, I start by visualizing the probability density function (PDF). 
The following DATA step calls the PDF_ZIBeta function, then uses PROC SGPLOT to visualize the PDF curves.
Remember to run the code in the Appendix first!
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">data</span> PDF_Test;
%DefineParms;
dx = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;
<span style="color: #0000ff;">do</span> <span style="color: #0000ff;">x</span> = 1E-6, dx/<span style="color: #2e8b57; font-weight: bold;">2</span>, dx to <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">by</span> dx;
   <span style="color: #0000ff;">PDF</span> = PDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">end</span>;
t = <span style="color: #2e8b57; font-weight: bold;">0</span>; <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">keep</span> p0 <span style="color: #0000ff;">x</span> <span style="color: #0000ff;">PDF</span> Params t;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;PDF of Zero-Inflated Beta&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=PDF_Test;
   series <span style="color: #0000ff;">x</span>=<span style="color: #0000ff;">x</span> y=<span style="color: #0000ff;">PDF</span> / <span style="color: #0000ff;">group</span>=Params lineattrs=<span style="color: #66cc66;">&#40;</span>thickness=<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   dropline <span style="color: #0000ff;">x</span>=t y=p0 / dropto=<span style="color: #0000ff;">x</span> lineattrs=GraphData1<span style="color: #66cc66;">&#40;</span>thickness=<span style="color: #2e8b57; font-weight: bold;">5</span><span style="color: #66cc66;">&#41;</span>;
   yaxis grid <span style="color: #0000ff;">min</span>=<span style="color: #2e8b57; font-weight: bold;">0</span> offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span>;
   xaxis grid;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<p>
The graph is shown at the top of this article.
I used a DROPLINE statement to display a vertical bar, which indicates the discrete probability mass at x=0.
The Beta curve defines the probability density for x &gt; 0.
</p>

<h3>The ZI Beta CDF</h3>
<p>
The cumulative distribution function (CDF) is the integral of the PDF. 
Given a value, x, the CDF at x tells you the probability that a random observation drawn from the ZI Beta distribution will be less than or equal to x. 
Because of the point-mass at 0, the CDF for the ZI Beta distribution has the value p0 (here, 0.2) at x=0 and increases monotonically as x increases.
The following DATA step calls the CDF_ZIBeta function,
then uses PROC SGPLOT to visualize the CDF curves:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">data</span> CDF_Test;
%DefineParms;
dx = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;
<span style="color: #0000ff;">do</span> <span style="color: #0000ff;">x</span> = 1E-6, dx/<span style="color: #2e8b57; font-weight: bold;">2</span>, dx to <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">by</span> dx;
   <span style="color: #0000ff;">CDF</span> = CDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">end</span>;
t = <span style="color: #2e8b57; font-weight: bold;">0</span>; <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">keep</span> p0 <span style="color: #0000ff;">x</span> <span style="color: #0000ff;">CDF</span> Params t;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;CDF of Zero-Inflated Beta&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=CDF_Test;
   series <span style="color: #0000ff;">x</span>=<span style="color: #0000ff;">x</span> y=<span style="color: #0000ff;">CDF</span> / <span style="color: #0000ff;">group</span>=Params lineattrs=<span style="color: #66cc66;">&#40;</span>thickness=<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   dropline <span style="color: #0000ff;">x</span>=t y=p0 / dropto=<span style="color: #0000ff;">x</span> lineattrs=GraphData1<span style="color: #66cc66;">&#40;</span>thickness=<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   yaxis grid <span style="color: #0000ff;">min</span>=<span style="color: #2e8b57; font-weight: bold;">0</span> offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span>;
   xaxis grid;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta2.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60165" srcset="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta2.png 640w, https://blogs.sas.com/content/iml/files/2026/09/ZIBeta2-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
For any value of x along the horizontal axis, the graph shows the probability (on the vertical axis) 
that a random observation is less than or equal to  x.
For example, the function at x=0.4 shows that there is approximately a 63% chance that a random variate from that 
distribution will have a value less than or equal to 0.4. Notice that there is a 20% probability that a random observation is 0.
</p>


<h3>The ZI Beta quantile function</h3>
<p>
The quantile function is the inverse of the CDF function.  
Given a probability, <em>p</em> in (0,1), the quantile function at <em>p</em> is the <em>x</em> value such that a random observation drawn 
from the ZI Beta distribution will be less than or equal to <em>x</em> with probability <em>p</em>. 
For a ZI distribution, you can use the quantile function of the continuous distribution,
but you need to rescale the probability to account for the point-mass at 0.
The following DATA step calls the quantile_ZIBeta function,
then uses PROC SGPLOT to visualize the quantile curves.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">data</span> Quantile_Test;
%DefineParms;
dp = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;
<span style="color: #0000ff;">do</span> p = <span style="color: #2e8b57; font-weight: bold;">0</span> to <span style="color: #2e8b57; font-weight: bold;">1</span>-dp <span style="color: #0000ff;">by</span> dp, <span style="color: #2e8b57; font-weight: bold;">0.999</span>, <span style="color: #2e8b57; font-weight: bold;">0.9999</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>;
   <span style="color: #0000ff;">x</span> = quantile_ZIBeta<span style="color: #66cc66;">&#40;</span>p, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">end</span>;
<span style="color: #0000ff;">keep</span> p0 p <span style="color: #0000ff;">x</span> Params;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Quantile Function of Zero-Inflated Beta&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=Quantile_Test;
   series <span style="color: #0000ff;">x</span>=p y=<span style="color: #0000ff;">x</span> / <span style="color: #0000ff;">group</span>=Params lineattrs=<span style="color: #66cc66;">&#40;</span>thickness=<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   yaxis grid <span style="color: #0000ff;">min</span>=<span style="color: #2e8b57; font-weight: bold;">0</span> offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span>;
   xaxis grid;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta3.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60162" srcset="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta3.png 640w, https://blogs.sas.com/content/iml/files/2026/09/ZIBeta3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
Notice that the graph of the quantile function is the "flipped image" of the CDF graph. 
Notice also that the derivative of the quantile becomes infinite as x &rarr; 1. 
This is because the slope of the CDF function approaches 0 as x &rarr; 1. 
</p>

<h3>Random variates from the ZI Beta distribution</h3>
<p>
For any mixture distribution, generating random variates requires two steps. The first step is to generate a binary
random variate. (In SAS, this is done by using the "Bernoulli" distribution.) With probability &pi;, you return a random variate from the first distribution; with probability 1-&pi;, you return a random variate from the second distribution. For the ZI Beta distribution, this means that 
you return 0 with probability &pi;, and a random variate from the Beta distribution with probability 1-&pi;. 
If you draw a histogram of the resulting random variates, the bin that includes 0 will display the counts of the random variates
near 0 from both distributions. 
</p><p>
The following DATA step calls the rand_ZIBeta function 1000 times. Approximately 20% of the random variates are from the point-mass at 0.
The remaining variates are from the Beta distribution. 
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">data</span> Rand_Test;
%DefineParms;
<span style="color: #0000ff;">call</span> streaminit<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1234</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">N</span> = <span style="color: #2e8b57; font-weight: bold;">1000</span>;
<span style="color: #0000ff;">do</span> j  = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff;">N</span>;
   <span style="color: #0000ff;">x</span> = rand_ZIBeta<span style="color: #66cc66;">&#40;</span>p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">output</span>;
<span style="color: #0000ff;">end</span>;
<span style="color: #0000ff;">keep</span> <span style="color: #0000ff;">x</span> Params;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Random Variates for the Zero-Inflated Beta&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=Rand_Test;
   histogram <span style="color: #0000ff;">x</span>;
   yaxis grid;
   xaxis grid;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta4.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60159" srcset="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta4.png 640w, https://blogs.sas.com/content/iml/files/2026/09/ZIBeta4-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
Because the program generates 1,000 random variates, the shape of the histogram is similar to the shape of the PDF curve. Specifically, note that bar that includes x=0 is slightly more than 20% for this random sample.
</p>

<h3>Define the functions for the ZI Beta distribution in SAS IML</h3>
<p>
Some programmers prefer to use the SAS IML language, especially when conducting a simulation study. Appendix 2 presents SAS IML module definitions. You can store the definition, then call them from a SAS IML program.
</p>

<h3>Summary</h3>
<p>
This article demonstrates how to implement a zero-inflated distribution in SAS. This article implements the
zero-inflated Beta distribution, but you can use the same techniques for other zero-inflated distributions. 
Appendix A of this article defines four related functions in PROC FCMP in Base SAS: the PDF, CDF, quantile, and random variate functions. SAS IML versions of the functions are defined in Appendix B.
</p>



<h3>Appendix A: FCMP functions for the zero-inflated Beta distribution</h3>
<p>
This appendix shows <a href="https://blogs.sas.com/content/iml/2012/04/18/extending-sas-how-to-define-new-functions-in-proc-fcmp-and-sasiml-software.html">how to use PROC FCMP in SAS to add new DATA step functions</a>. It follows the same technique as <a href="https://blogs.sas.com/content/iml/2025/08/25/gev-sas.html">a previous article about the generalized extreme-value (GEV) distribution</a>.
Run this PROC FCMP step before trying to call the functions in the DATA step.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Define the PDF, CDF, QUANTILE, and RAND functions for the 
   ZI Beta distribution in PROC FCMP.  
&nbsp;
   The support for the ZI Beta distribution is X in [0,1).
   alpha : Shape1 &gt; 0
   beta  : Shape2 &gt; 0
&nbsp;
   For simplicity, the function assume alpha &gt; 0 and beta &gt; 0.
   The PDF is undefined if x=1 and beta &lt; 1. It is well-defined
   for other parameter values. For simplicity, we always 
   return a missing value with x=1. Thus, the variates for
   the distribution are defined on [0,1). */</span>
<span style="color: #000080; font-weight: bold;">proc fcmp</span> outlib=work.funcs.ProbDist;
   function PDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> <span style="color: #0000ff;">x</span> &lt; <span style="color: #2e8b57; font-weight: bold;">0</span> | <span style="color: #0000ff;">x</span> &gt;= <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>.<span style="color: #66cc66;">&#41;</span>;
      f = ifn<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>=<span style="color: #2e8b57; font-weight: bold;">0</span>, p0, <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span><span style="color: #006400; font-style: italic;">*pdf('Beta', x, alpha, beta));</span>
      <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>f<span style="color: #66cc66;">&#41;</span>;
   endsub;
&nbsp;
   function CDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> <span style="color: #0000ff;">x</span> &lt; <span style="color: #2e8b57; font-weight: bold;">0</span> | <span style="color: #0000ff;">x</span> &gt;= <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>.<span style="color: #66cc66;">&#41;</span>;
      F = ifn<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>=<span style="color: #2e8b57; font-weight: bold;">0</span>, p0, p0+<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span><span style="color: #006400; font-style: italic;">*cdf('Beta', x, alpha, beta));</span>
      <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>F<span style="color: #66cc66;">&#41;</span>;
   endsub;
&nbsp;
   function quantile_ZIBeta<span style="color: #66cc66;">&#40;</span>p, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> p &lt; <span style="color: #2e8b57; font-weight: bold;">0</span> | p &gt; <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>.<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> p &lt;= p0 <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> p = <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
      z = <span style="color: #66cc66;">&#40;</span>p-p0<span style="color: #66cc66;">&#41;</span>/<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* rescale the probability on [p0,1] */</span>
      <span style="color: #0000ff;">x</span> = quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'Beta'</span>, z, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   endsub;
&nbsp;
   function rand_ZIBeta<span style="color: #66cc66;">&#40;</span>p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      b = rand<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Bernoulli&quot;</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">if</span> b=<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">x</span> = rand<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'Beta'</span>, alpha, beta<span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   endsub;
<span style="color: #000080; font-weight: bold;">quit</span>;
<span style="color: #0000ff;">options</span> cmplib=work.funcs;  <span style="color: #006400; font-style: italic;">/* define location of functions so the DATA step can find them */</span></pre></td></tr></table></div>





<h3>Appendix B: SAS IML functions for the zero-inflated Beta distribution</h3>
<p>
This appendix shows how to define the PDF, CDF, QUANTILE, and RAND functions for the ZI Beta distribution in the SAS IML language.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* PDF of Zero Inflated Beta Distribution
   x can be a vector; p0, alpha, and beta are scalars. */</span>
start PDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   f = j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span> = <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      f<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = p0;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span> &lt; <span style="color: #0000ff;">x</span> &amp; <span style="color: #0000ff;">x</span> &lt; <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      f<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span><span style="color: #006400; font-style: italic;">*PDF('beta', x[idx], alpha, beta);</span>
   <span style="color: #0000ff;">return</span> f;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* CDF of Zero Inflated Beta Distribution
   x can be a vector; p0, alpha, and beta are scalars. */</span>
start CDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   F = j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span> = <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      F<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = p0;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span> = <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      F<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span> &lt; <span style="color: #0000ff;">x</span> &amp; <span style="color: #0000ff;">x</span> &lt; <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      F<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = p0 + <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span><span style="color: #006400; font-style: italic;">*CDF('beta', x[idx], alpha, beta);</span>
   <span style="color: #0000ff;">return</span> F;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Quantile function of Zero Inflated Beta Distribution
   p can be a vector in (0,1); p0, alpha, and beta are scalars. */</span>
start Quantile_ZIBeta<span style="color: #66cc66;">&#40;</span>p, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">x</span> = j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span>p<span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span>p<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span> &lt;= p &amp; p &lt;= p0<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">0</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span>p = <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span>p0 &lt; p &amp; p &lt; <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
      z = <span style="color: #66cc66;">&#40;</span>p<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span>-p0<span style="color: #66cc66;">&#41;</span>/<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* rescale the probability on [p0,1] */</span>
      <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'Beta'</span>, z, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">x</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Random variate function of Zero Inflated Beta Distribution
   N is the number or random variates; p0, alpha, and beta are scalars. */</span>
start Rand_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">x</span> = j<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>, .<span style="color: #66cc66;">&#41;</span>;
   b = randfun<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, <span style="color: #a020f0;">'Bernoulli'</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>-p0<span style="color: #66cc66;">&#41;</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span>b = <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">0</span>;
   idx = loc<span style="color: #66cc66;">&#40;</span>b = <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   nBeta = ncol<span style="color: #66cc66;">&#40;</span>idx<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> nBeta&gt;<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span>
      <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span> = randfun<span style="color: #66cc66;">&#40;</span>nBeta, <span style="color: #a020f0;">'Beta'</span>, alpha, beta<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
finish;
store module=<span style="color: #66cc66;">&#40;</span>PDF_ZIBeta CDF_ZIBeta quantile_ZIBeta Rand_ZIBeta<span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Test the functions for the ZI Beta distribution */</span>
<span style="color: #000080; font-weight: bold;">proc iml</span>;
load module=<span style="color: #66cc66;">&#40;</span>PDF_ZIBeta CDF_ZIBeta quantile_ZIBeta Rand_ZIBeta<span style="color: #66cc66;">&#41;</span>;
&nbsp;
p0    = <span style="color: #2e8b57; font-weight: bold;">0.2</span>;
alpha = <span style="color: #2e8b57; font-weight: bold;">2</span>;
beta  = <span style="color: #2e8b57; font-weight: bold;">3</span>;
&nbsp;
dx = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;
<span style="color: #0000ff;">x</span> = <span style="color: #2e8b57; font-weight: bold;">0</span> // 1E-6 // T<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">do</span><span style="color: #66cc66;">&#40;</span>dx, <span style="color: #2e8b57; font-weight: bold;">1</span>, dx<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">PDF</span> = PDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;PDF of Zero-Inflated Beta&quot;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, <span style="color: #0000ff;">PDF</span><span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">x</span> y<span style="color: #66cc66;">&#125;</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;CDF of Zero-Inflated Beta&quot;</span>;
<span style="color: #0000ff;">CDF</span> = CDF_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, <span style="color: #0000ff;">CDF</span><span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">x</span> y<span style="color: #66cc66;">&#125;</span> other=<span style="color: #a020f0;">&quot;refline 0.2/axis=y; yaxis grid values=(0 to 1 by 0.1);&quot;</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Quantile Function of Zero-Inflated Beta&quot;</span>;
dp = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;
p = T<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">do</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span>, p0, dp<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> // T<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">do</span><span style="color: #66cc66;">&#40;</span>p0+dp/<span style="color: #2e8b57; font-weight: bold;">10</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>, dp<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
q = quantile_ZIBeta<span style="color: #66cc66;">&#40;</span>p, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span>p, q<span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">x</span> y<span style="color: #66cc66;">&#125;</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Random Variates for the Zero-Inflated Beta&quot;</span>;
<span style="color: #0000ff;">x</span> = Rand_ZIBeta<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1000</span>, p0, alpha, beta<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> histogram<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span>y<span style="color: #66cc66;">&#125;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;</pre></td></tr></table></div>



<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/14/zero-inflated-beta-sas.html">Implement a zero-inflated distribution in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/09/14/zero-inflated-beta-sas.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/09/ZIBeta1-150x150.png" />
	</item>
		<item>
		<title>How to evaluate the multivariate t CDF in SAS</title>
		<link>https://blogs.sas.com/content/iml/2026/09/08/multivariate-t-cdf.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/09/08/multivariate-t-cdf.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Tue, 08 Sep 2026 09:28:29 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Numerical Analysis]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=60009</guid>

					<description><![CDATA[<p>The classical multivariate normal (MVN) distribution is a standard model for correlated data. It is a simple model, it is easy to fit the MVN model to data, and the parameters in the model (locations and correlations) are intuitive. Of course, normality is a strong assumption that is not always [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/08/multivariate-t-cdf.html">How to evaluate the multivariate t CDF in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
The classical multivariate normal (MVN) distribution is a standard 
model for correlated data. It is a simple model, it is easy to fit the MVN model to data, and the parameters in the model (locations and correlations) are intuitive. Of course, normality is a strong assumption that is not always satisfied by real-world data,
which might have skewness and extreme-tail behavior. The normal distribution (whether univariate or multivariate) is an example of a "thin-tailed" model because the probability of an extreme value declines to zero exponentially fast as a function of the distance to the center of the data.
</p><p>
Certain fields, such as quantitative finance and climatology, routinely use models with thicker tails. 
If you need to analyze data that have "heavy tails," one possible choice is the multivariate t (MVT) distribution. 
The MVT distribution has a parameter, &nu;, that controls the tail thickness.
For historical reasons, &nu; is called the <em>degrees-of-freedom parameter</em>. When used for inferential statistics, the parameter is an integer.
However, for the MVT distribution, &nu; is a shape parameter that is not restricted to integer values.
For small values of &nu;, the MVT provides a heavy-tailed alternative to the MVN distribution.  As &nu; &rarr; &infin;, the MVT 
distribution approaches the multivariate normal distribution.
</p><p>
I have previously written about <a href="https://blogs.sas.com/content/iml/2022/06/29/multivariate-t-density.html">how to visualize the multivariate t distribution in SAS</a>, including how to 
evaluate the multivariate t PDF and how to generate random variates from MVT by using the RANDMVT function in SAS IML software.
This article discusses how to compute the cumulative distribution function (CDF) for the multivariate t distribution.
The CDF requires computing a high-dimensional integral.
The article includes a SAS IML function that evaluates the CDF for the multivariate t distribution in two dimensions.
</p>

<h3>A general formula for the CDF of the multivariate t distribution</h3>
<p>
This section presents the advanced math that enables you to compute the CDF for the multivariate t (MVT) distribution.
If you prefer programming over math, you can skip to the next sections.
</p><p>
The book <em>Computation of Multivariate Normal and t Probabilities</em> (Genz and Bretz, 2009) shows that you can 
obtain the CDF of the standard MVT(&Sigma;, &nu;) distribution as an integral of the CDF of the MVN(0, &Sigma;) distribution. 
The goal is to evaluate the probability that a k-dimensional random variable X ~ MVT(&Sigma;, &nu;) is in a
hyper-rectangular region: P(a1 &lt; X1 &lt; b1, a2 &lt; X2 &lt; b2, ..., ak &lt; Xk &lt; bk).
Here &Sigma; is a <em>k</em>&nbsp;x&nbsp;<em>k</em> positive semidefinite covariance matrix that defines the correlations between the components of X.
</p><p>The vectors 
<strong>a</strong> = (a1, a2, ..., ak) and <strong>b</strong> = (b1, b2, ..., bk) 
define the rectangular region. To obtain the probability that a random variate is in this region, you use the components of 
<strong>a</strong> for the lower limits of integration, and the components of <strong>b</strong> for the upper limits. The components of <strong>a</strong> can be -&infin;, and the components of <strong>b</strong> can be +&infin;.
</p>
<p>
Although you can define the probability directly as a multivariate integral over the hyper-rectangular region, Genz and Bretz show that you 
can also compute the probability by integrating a function that includes the CDF for a MVN distribution.
The first formula appears on p. 3 and is listed as Eqn 1.3.
The formula shows how to compute the MVT CDF by integrating a related CDF for the MVN distribution over the 
infinite interval (0, &infin;). Then,
in Chapter 4 (Eqn 4.6, p. 32-33),
Genz and Bretz show that you can transform the integral on the infinite interval (0, &infin;) into an equivalent integral on (0,1).
The formula is:
<br />
<span class='MathJax_Preview'>\(
T_k(\mathbf{a}, \mathbf{b}; \boldsymbol{\Sigma}, \nu) = 
\int_{0}^{1} \Phi_k \left( \frac{\chi_\nu^{-1}(t)\mathbf{a}}{\sqrt{\nu}}, \frac{\chi_\nu^{-1}(t)\mathbf{b}}{\sqrt{\nu}}; 
\boldsymbol{\Sigma} \right) dt 
\)</span><script type='math/tex'>
T_k(\mathbf{a}, \mathbf{b}; \boldsymbol{\Sigma}, \nu) = 
\int_{0}^{1} \Phi_k \left( \frac{\chi_\nu^{-1}(t)\mathbf{a}}{\sqrt{\nu}}, \frac{\chi_\nu^{-1}(t)\mathbf{b}}{\sqrt{\nu}}; 
\boldsymbol{\Sigma} \right) dt 
</script>
</p>

<p>In this formula:
</p>
<ul>
<li>T<sub>k</sub>(<strong>a</strong>, <strong>b</strong>; &Sigma;, &nu;) is the CDF function for the MVT distribution. It gives the probability that a random variate from MVT(&Sigma;, &nu;) is in the region defined by the vectors <strong>a</strong> and <strong>b</strong>.
</li>
<li>&Phi;<sub>k</sub> is the MVN CDF in dimension <em>k</em>.
<span class='MathJax_Preview'>\(\Phi_k (\mathbf{L}, \mathbf{U}; \boldsymbol{\Sigma})\)</span><script type='math/tex'>\Phi_k (\mathbf{L}, \mathbf{U}; \boldsymbol{\Sigma})</script> is the probability that a random variable from the centered MVN distribution is 
in the hyper-rectangular region defined by the lower-limit vector, <strong>L</strong>, and the upper-limit vector, <strong>U</strong>.
The vector <strong>L</strong> = <span class='MathJax_Preview'>\(\frac{\chi_\nu^{-1}(t)\mathbf{a}}{\sqrt{\nu}}\)</span><script type='math/tex'>\frac{\chi_\nu^{-1}(t)\mathbf{a}}{\sqrt{\nu}}</script>, 
and
<strong>U</strong> = <span class='MathJax_Preview'>\(\frac{\chi_\nu^{-1}(t)\mathbf{b}}{\sqrt{\nu}}\)</span><script type='math/tex'>\frac{\chi_\nu^{-1}(t)\mathbf{b}}{\sqrt{\nu}}</script>, where t is the dummy integration variable in (0,1). 
</li>
<li>
&chi;<sub>&nu;</sub><sup>-1</sup> is the quantile function for 
<a href="https://en.wikipedia.org/wiki/Chi_distribution">the chi distribution</a>. 
If you've never heard of the chi distribution, don't worry. It is closely related to the familiar chi-square distribution.
In fact, if Y ~ &chi;<sup>2</sup>(&nu;) is distributed as a chi-squared distribution with &nu; degrees of freedom, then 
X = sqrt(Y) is distributed as a chi distribution with &nu; degrees of freedom. This definition means that you can use the 
QUANTILE("CHISQ") distribution in Base SAS to compute the quantile function of the chi distribution. You simply take the 
square-root of the resulting quantile!
</li>
</ul>

<p>
This formula enables you to compute the exact CDF of a MVT distribution by integrating a function that involves only the (transformed) probability of the MVN distribution over the interval (0,1). In SAS, 
<a href="https://blogs.sas.com/content/iml/2023/11/29/bivariate-normal-rectangle.html">you can use the PROBBNRM function to evaluate 2-D MVN CDF</a>. And SAS IML software enables you to integrate arbitrary functions!
So, let's write a SAS IML function that uses the formula to evaluate probabilities for the 2-D MVT distribution.
The function will enable you to compute exact CDF values in this case.
</p>

<h3>The CDF of the bivariate t distribution in SAS</h3>

<a href="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3.png" alt="" width="360" height="360" class="alignright size-full wp-image-60054" srcset="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3.png 480w, https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3-300x300.png 300w, https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3-150x150.png 150w" sizes="(max-width: 360px) 100vw, 360px" /></a>

<p>
The formula for Eqn 4.6 provides the CDF on any rectangular region. However, for ease of presentation, I will present the 
simpler case of the "left-tailed" CDF in 2-D, which is the probability that a random variable is in the region (-&infin;, b1)x(-&infin;, b2).
In other words, I will set the vector of lower limits to <strong>a</strong> = (-&infin;, -&infin;).
<a href="https://blogs.sas.com/content/iml/2023/11/29/bivariate-normal-rectangle.html">This computation can be used to compute the probability of general rectangular regions</a>.
</p><p>

A left-tailed region is shown in the image to the right. In the image, I generated 1,000 random variates according to a bivariate t distribution with &nu;=5 degrees of freedom. Of those points, 180 of them fall into the region {(x,y) | x &lt; -1, y &lt; 0}, which means that a Monte Carlo estimate for the probability is 0.18.
Let's use the formulas in the previous section to obtain a better estimate of the probability.
</p><p>
You can use the QUAD routine in the SAS IML language to perform numerical integration.
It is helpful to <a href="https://blogs.sas.com/content/iml/2014/08/13/peak-option-in-quad.html">use the PEAK= option</a> 
to tell the QUAD routine to avoid evaluating the integral near the boundary of the integration region.
The following IML program defines and store three functions. The BVT_CDF is the top-level function that evaluates the bivariate t CDF.
'BVT_CDF' is short for 'bivariate t CDF'.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* A useful result in computational statistics is Eqn 1.3 and Eqn 4.6 in 
   Genz and Bretz (2009, Computation of Multivariate Normal and t Probabilities, p. 3, 32-33).
   The equation shows that you can compute any multivariate t CDF by computing an integral
   that uses the multivariate normal (MVN) CDF. 
   Specifically, Eqn 4.6 shows that you can use the quantile of the CHI distribution 
   (CHI, not chi-squared!) to transform Eqn 1.3 into an integral on the interval (0,1).
*/</span>
<span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* truncate every element of x into the interval [ab[1], ab[2]].
   See https://blogs.sas.com/content/iml/2026/02/04/clip-values.html
*/</span>
start TruncateToRange<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, ab<span style="color: #66cc66;">&#41;</span>;
   a = ab<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>; b = ab<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>a &lt;&gt; <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span> &gt;&lt; b <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Define the integrand from Equation (4.6) in Genz (2009)
   by calling the PROBBNRM function in Base SAS to compute the bivariate normal CDF.
   For numerical reasons, limit the evaluation of PROBBNRM to [-10,10] in each coordinate. 
   In case nu is large, use the exp(log(.)) trick to evaluate the constants.
*/</span>
start BVT_CDF_Integrand<span style="color: #66cc66;">&#40;</span>t<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_b, g_rho, g_nu<span style="color: #66cc66;">&#41;</span>;
   b = g_b; rho = g_rho; nu = g_nu;
   <span style="color: #0000ff;">if</span>      t&lt;=<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> s=<span style="color: #2e8b57; font-weight: bold;">0</span>;
   <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> t&gt;=<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> s=1E6;
   <span style="color: #0000ff;">else</span> s = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span> quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;ChiSq&quot;</span>, t, nu<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
   w = s <span style="color: #006400; font-style: italic;">* b / sqrt(nu);</span>
   w = TruncateToRange<span style="color: #66cc66;">&#40;</span>w, <span style="color: #66cc66;">&#123;</span>-<span style="color: #2e8b57; font-weight: bold;">10</span>, <span style="color: #2e8b57; font-weight: bold;">10</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* protect against floating point underflow; max value is 10 */</span>
   f = probbnrm<span style="color: #66cc66;">&#40;</span>w<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>, w<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>, rho<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> f <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* wrapper function to set global variables and call the integration */</span>
start BVT_CDF<span style="color: #66cc66;">&#40;</span>b, rho, nu<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_b, g_rho, g_nu<span style="color: #66cc66;">&#41;</span>;
   g_b = b; g_rho = rho; g_nu = nu;
   <span style="color: #0000ff;">call</span> quad<span style="color: #66cc66;">&#40;</span>BVT_prob, <span style="color: #a020f0;">&quot;BVT_CDF_Integrand&quot;</span>, <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#41;</span> peak=<span style="color: #2e8b57; font-weight: bold;">0.1</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> BVT_prob <span style="color: #66cc66;">&#41;</span>;
finish;
store module=<span style="color: #66cc66;">&#40;</span>TruncateToRange BVT_CDF_Integrand BVT_CDF<span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;</pre></td></tr></table></div>




<p>
Having defined the functions, you can now call them to compute the CDF for the bivariate t distribution, as follows:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
load module=<span style="color: #0000ff;">_all_</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* call the BVT_CDF with different values of b and nu */</span>
b = <span style="color: #66cc66;">&#123;</span>-<span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#125;</span>;     <span style="color: #006400; font-style: italic;">/* upper limits for X1 and X2 */</span>
rho = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>/<span style="color: #2e8b57; font-weight: bold;">2</span>; <span style="color: #006400; font-style: italic;">/* correlation */</span>
nu = <span style="color: #2e8b57; font-weight: bold;">5</span>;          <span style="color: #006400; font-style: italic;">/* DOF */</span>
<span style="color: #006400; font-style: italic;">/* first, compute the probability for the MVN distribution */</span>
BVN_prob = probbnrm<span style="color: #66cc66;">&#40;</span>b<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>, b<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>, rho<span style="color: #66cc66;">&#41;</span>;
print <span style="color: #66cc66;">&#40;</span>b`<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'b1'</span> <span style="color: #a020f0;">'b2'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span> rho BVN_prob<span style="color: #66cc66;">&#91;</span>L=<span style="color: #a020f0;">&quot;Bivariate Normal CDF&quot;</span><span style="color: #66cc66;">&#93;</span>;   
&nbsp;
<span style="color: #006400; font-style: italic;">/* compare to the MVT probability (nu=5) */</span>
BVT_prob = BVT_CDF<span style="color: #66cc66;">&#40;</span>b, rho, nu<span style="color: #66cc66;">&#41;</span>;
print nu <span style="color: #66cc66;">&#40;</span>b`<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'b1'</span> <span style="color: #a020f0;">'b2'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span> rho BVT_prob<span style="color: #66cc66;">&#91;</span>L=<span style="color: #a020f0;">&quot;Bivariate t CDF&quot;</span><span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob1.png" alt="" width="249" height="137" class="alignnone size-full wp-image-60060" />

<p>
For these calculations, we assume that two random variables have the correlation 0.7071.
The first output uses the built-in PROBBNRM function in SAS to compute the probability
P(Z1 &lt; -1, Z2 &lt; 0) for Z ~ MVN(0, R), where R is the correlation matrix with off-diagonal elements &rho;.
The output shows the bivariate normal CDF at (-1,0) is 0.146.
In contrast, the CDF for the bivariate t distribution with 5 DoF is 0.162 because a t distribution has heavier tails.
This is the probability
P(T1 &lt; -1, T2 &lt; 0) for T ~ MVT(0; R, &nu;).
This value is much more accurate than the Monte Carlo estimate shown in the previous section. In addition, the Monte Carlo estimate suffers from variability: If you choose a different set of 1,000 random points, you will get a different Monte Carlo estimate. In contrast, the integration method is <em>deterministic</em> and highly accurate.
</p><p>
If you reverse the correlation of the variables, you get two negatively correlated variables. 
You can compute the CDF probability for the MVT distribution with &rho; = -0.7071 by using the following call: 
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">rho = -<span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>/<span style="color: #2e8b57; font-weight: bold;">2</span>; <span style="color: #006400; font-style: italic;">/* correlation */</span>
BVT_prob2 = BVT_CDF<span style="color: #66cc66;">&#40;</span>b, rho, nu<span style="color: #66cc66;">&#41;</span>;
print nu <span style="color: #66cc66;">&#40;</span>b`<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'b1'</span> <span style="color: #a020f0;">'b2'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span> rho BVT_prob2<span style="color: #66cc66;">&#91;</span><span style="color: #0000ff;">label</span>=<span style="color: #a020f0;">&quot;Bivariate t CDF&quot;</span><span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob2.png" alt="" width="241" height="67" class="alignnone size-full wp-image-60057" />

<p>
The probability in the region is about 0.019, which is smaller than for the positively correlated variables. 
You can understand this smaller number by looking back at the scatter plot in the previous section. 
That scatter plot has a southwest-to-northeast orientation.
When you reverse the correlation, the analogous scatter plot will have a 
southeast-to-northwest orientation. (Mentally flip the scatter plot about the vertical line x=0.) 
The new scatter plot will have only a small proportion of points (about 19 per thousand) that fall into the specified
region.
</p>

<h3>Summary</h3>
<p>
This article shows a direct method to evaluate the CDF for the bivariate t distribution in SAS.
The computation relies on a mathematical result in Genz and Bretz (2009), which shows that you 
can evaluate the probability that a MVT variable (in any dimension) is in a rectangular region by integrating the 
CDF for the MVN distribution over the interval (0, 1).  To simplify the presentation, I showed the 
special case of computing the CDF for the bivariate t distribution on left-tailed regions 
{(T1,T2) | T1 &lt; b1, T2 &lt; b2}, where T = (T1,T2) is a random variable distributed as MVT(&Sigma;, &nu;).
You can use this left-tail probability to compute the probability in any other 2-D rectangular region. 
</p><p>
It is difficult to compute the exact CDF of the MVN distribution in <em>k</em> dimensions when <em>k</em> &gt; 2. 
However, you can use the formula and numerical methods such as quasi-Monte Carlo computations to compute the CDF of the MVT distribution 
to high precision.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/08/multivariate-t-cdf.html">How to evaluate the multivariate t CDF in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/09/08/multivariate-t-cdf.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/09/MVT_prob3-150x150.png" />
	</item>
		<item>
		<title>An easy way to create a correlation matrix with negative correlations</title>
		<link>https://blogs.sas.com/content/iml/2026/08/31/change-sign-corr-matrix.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/08/31/change-sign-corr-matrix.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 31 Aug 2026 09:26:33 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Matrix Computations]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59964</guid>

					<description><![CDATA[<p>A previous article discusses how to generate a random correlation matrix. On average, in a random correlation matrix, half of the off-diagonal entries are negative and half are positive. For any realization, the proportion of negative correlations might be greater than (or less than) half. This is in contrast to [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/31/change-sign-corr-matrix.html">An easy way to create a correlation matrix with negative correlations</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
<a href="https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html">A previous article discusses how to generate a random correlation matrix</a>. 
On average, in a random correlation matrix, half of the off-diagonal entries are negative and half are positive.
For any realization, the proportion of negative correlations might be greater than (or less than) half.
This is in contrast to many of the <a href="https://blogs.sas.com/content/iml/2022/12/14/heterogeneous-covariance-matrices.html">well-known structured matrices that you encounter in statistics</a>. 
The structured matrices often depend on a parameter, often called rho. If rho is positive, then all elements of the correlation matrix are positive. 
When rho is negative, the number of negative correlations depends on the structure and the size of the matrix.
</p><p>
It is straightforward to modify any correlation matrix to change the number of positive and negative values in the off-diagonal locations.
This article shows the mathematics and a simple SAS IML function.
The technique enables you to create a new correlation matrix from one that you already have.
The entries in the new correlation matrix have the same magnitude, but different signs.
</p>

<h3>A simple transformation that changes correlation</h3>
<p>
Let X and Y be data vectors. (Or, they could be random variables if you prefer a more rigorous mathematical treatment.)
If the correlation &rho; = Corr(X,Y) is not zero, then Corr(-X,Y) has the opposite sign from &rho;.
In other words, multiplying a variable by -1 changes the sign of the correlation with other variables in the data set.
</p><p>
Of course, you could also change the sign of Y. If R is a 2x2 correlation matrix with off-diagonal element &rho;, then you can construct a
new correlation matrix by using the similarity transformation Q = S*R*S`, where S is a diagonal 2x2 "sign matrix." The S matrix contains &plusmn;1 on the diagonal.
If S has one -1 on the diagonal, then Q has -&rho; on the off-diagonal. Otherwise, Q has +&rho; for the off-diagonal element.
</p>
<p>
You can extend this result to more variables. If R is a dxd correlation matrix, and S is any diagonal sign matrix with values &plusmn;1, then Q = S*R*S`
is also a correlation matrix. The (i,j)th element of Q is Q[i,j] = S[i]*R[i,j]*S[j]= &plusmn;R[i,j].
Mathematically, <a href="https://en.wikipedia.org/wiki/Sylvester%27s_law_of_inertia">Sylvester's Theorem</a> ensures that Q is a valid correlation matrix. If R is symmetric and positive semi-definite, then so is Q.
</p><p>
Of course, if S is a symmetric matrix, then the similarity transformation simplifies to S*R*S, which is the form that I use in the next section.
</p>

<h3>A SAS IML program that changes correlation</h3>
<p>
The following program defines a function that computes a similarity matrix.
Assume that R is a dxd matrix and v is a dx1 vector. The function returns the matrix  S*R*S` where S = diag(v).
I immediately call this function for the special case where R is a correlation matrix, and v[i]= &plusmn;1.
</p><p>
I use a programming technique that is worth noticing. You should <a href="https://blogs.sas.com/content/iml/2014/05/19/never-multiply-with-a-large-diagonal-matrix.html">never multiply by a large diagonal matrix</a>.  Instead of diag(v)*R*diag(v), you can use the more efficient computation v#R#v`, where '#' indicates elementwise multiplication. This trick speeds up the computation and uses much less memory.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* Given a dxd matrix, R, and a dx1 vector v, return the similar matrix
   Q = diag(v)*R*diag(v).  
   An important application is to let v be a vector of +/-1. Then, 
   if R is a valid correlation matrix, Q is also a correlation matrix
   where some correlations have changed signs.
*/</span>
start SimilarMat<span style="color: #66cc66;">&#40;</span>R, v<span style="color: #66cc66;">&#41;</span>;
   S = colvec<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
   Q = S # R # S`;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span>Q<span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* --- Example Usage --- */</span>
<span style="color: #006400; font-style: italic;">/* Create AR(1) correlation matrix:
   https://blogs.sas.com/content/iml/2012/11/05/constructing-common-covariance-structures.html */</span>
start AR1Corr<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">dim</span>, rho<span style="color: #66cc66;">&#41;</span>;
   u = cuprod<span style="color: #66cc66;">&#40;</span>j<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>,dim-<span style="color: #2e8b57; font-weight: bold;">1</span>,rho<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* cumulative product */</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> toeplitz<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span> || u<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Create a 5x5 AR(1; 0.5) matrix */</span>
R = AR1Corr<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">5</span>, <span style="color: #2e8b57; font-weight: bold;">0.5</span><span style="color: #66cc66;">&#41;</span>;
v = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, -<span style="color: #2e8b57; font-weight: bold;">1</span>, -<span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>, -<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>;  <span style="color: #006400; font-style: italic;">/* change correlations for the 2nd, 3rd, and 5th variables */</span>
Q = SimilarMat<span style="color: #66cc66;">&#40;</span>R, v<span style="color: #66cc66;">&#41;</span>;
&nbsp;
origNames = <span style="color: #a020f0;">'X1'</span>:<span style="color: #a020f0;">'X5'</span>;
newNames  = <span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'X1'</span> <span style="color: #a020f0;">'-X2'</span> <span style="color: #a020f0;">'-X3'</span> <span style="color: #a020f0;">'X4'</span> <span style="color: #a020f0;">'-X5'</span><span style="color: #66cc66;">&#125;</span>;
print R<span style="color: #66cc66;">&#91;</span>c=origNames r=origNames F=BestD7.<span style="color: #66cc66;">&#93;</span>, 
      Q<span style="color: #66cc66;">&#91;</span>c=newNames r=newNames F=BestD7.<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/flipCorr1.png" alt="" width="268" height="200" class="alignnone size-full wp-image-59976" />

<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/flipCorr2.png" alt="" width="292" height="200" class="alignnone size-full wp-image-59973" />

<p>
The output shows that the original matrix, R, has all positive correlations. 
However, in the new matrix, the correlation in the (i,j)th cell has the sign v[i]*v[j].
This creates a correlation matrix that has negative values.
</p>

<h3>Summary</h3>
<p>
This article shows a simple trick for changing the signs of elements in a correlation matrix. If R is any 
correlation matrix and S = diag(v) is a diagonal sign matrix with values &plusmn;1, then the product
S*R*S` is a correlation matrix that has different signs than R. You can compute the product efficiently without ever forming a diagonal matrix.
This trick is useful in simulation studies and in creating matrices to test statistical procedures.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/31/change-sign-corr-matrix.html">An easy way to create a correlation matrix with negative correlations</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/08/31/change-sign-corr-matrix.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2025/01/spectrumViz4-150x150.png" />
	</item>
		<item>
		<title>A direct method to generate correlation matrices with specified eigenvalues</title>
		<link>https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html#comments</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 24 Aug 2026 09:24:07 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Simulation]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59808</guid>

					<description><![CDATA[<p>In a previous article, I implemented an algorithm due to Niels Waller (TAS, 2020) that uses the method of alternating projections (MAP) to generate random correlation matrices that have a specified set of eigenvalues. The algorithm is iterative, and the MAP method is not guaranteed to converge, although Waller claims [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html">A direct method to generate correlation matrices with specified eigenvalues</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
<a href="https://blogs.sas.com/content/iml/2024/12/18/correlation-matrix-eigenvalues.html">In a previous article</a>, I implemented an algorithm due to <a href="https://www.tandfonline.com/doi/full/10.1080/00031305.2017.1401960">Niels Waller (<em>TAS</em>, 2020)</a> that uses the method of alternating projections (MAP) to generate random correlation matrices that have a specified set of eigenvalues. The algorithm is iterative, and the MAP method is not guaranteed to converge, although Waller claims that the method worked well in a simulation study. MAP and other indirect methods tend to be computationally expensive, so I was happy to discover a paper by 
<a href="https://epubs.siam.org/doi/abs/10.1137/0905034">Marsaglia and Olkin (1984)</a> that uses a direct method to generate random correlation matrices. 
The Marsaglia and Olkin algorithm is straightforward to implement in a high-level language like SAS IML.
This article presents an IML function that implements the Marsaglia and Olkin direct method
for generating a random correlation matrix.
</p>

<h3>Correlation matrices that have a common spectrum</h3>
<p>
A matrix is positive semi-definite if all eigenvalues are greater than or equal to zero.
All correlation matrices are symmetric and positive semi-definite.
</p><p>
The spectrum of the matrix is the set of eigenvalues: &Lambda; = {&lambda;<sub>1</sub>, &lambda;<sub>2</sub>, ..., &lambda;<sub>d</sub>}.
For a positive definite matrix, &lambda;<sub>i</sub> &gt; 0 for all <em>i</em>.
For a semi-definite matrix, &lambda;<sub>i</sub> &ge; 0. 
For any square matrix, the sum of the eigenvalues equals the trace of the matrix. Consequently, for an <em>d</em>&nbsp;x&nbsp;d correlation matrix, &Sigma;<sub>i</sub> &lambda;<sub>i</sub> = <em>d</em>.
</p>
<p>
<a href="https://blogs.sas.com/content/iml/2024/12/18/correlation-matrix-eigenvalues.html">As shown in the previous article</a>, 
there are many different correlation matrices that have the same spectrum. 
</p>


<h3>The Marsaglia and Olkin method for random correlation matrices</h3>
<p>
The Marsaglia-Olkin (1984) paper is behind a paywall, so I have not read it. Fortunately, Gentle (2003, p. 200) presents pseudo-code for their algorithm.
Gentle's pseudo-code contains two typos (Steps 5 and 6), which I have corrected in the following description.
</p>
<p>
The input to the Marsaglia-Olkin routine is Lambda, which is the spectrum of a <em>d</em>&nbsp;x&nbsp;<em>d</em> SPD correlation matrix. Lambda is a <em>d</em>&nbsp;x&nbsp;1 vector
{&lambda;<sub>1</sub>, &lambda;<sub>1</sub>, ..., &lambda;<sub>d</sub>}, where 
&lambda;<sub>i</sub> &gt; 0 and &Sigma;<sub>i</sub> &lambda;<sub>i</sub> = <em>d</em>.
The algorithm outputs R, which is a random correlation matrix such that the eigenvalues of R are the specified spectrum.
The algorithm is as follows:
</p>

<ol start="0">
  <li>Set E = I<sub>d</sub> and k=1.</li>
  <li>Generate a <em>d</em>&nbsp;x&nbsp;1 vector, w, of i.i.d. standard normal random variates.<br />
    Form x = E*w and compute the scalar a = (1-Lambda)` * x##2</li>
  <li>Generate a <em>d</em>&nbsp;x&nbsp;1 vector, z, of i.i.d. standard normal random variates.<br />
    Form y = E*z and compute the scalars:
    <ul>
      <li>b = (1-Lambda)` * (x#y)</li>
      <li>c = (1-Lambda)` * y##2</li>
      <li>e<sup>2</sup> = b##2 - a*c</li>
    </ul>
  </li>
  <li>If e<sup>2</sup> &lt; 0, goto Step 2.</li>
  <li>Choose a random sign, s &isin; {-1, 1}. Set r = (b+s*e)/a * x - y.</li>
  <li>Choose another random sign, s &isin; {-1, 1}. Set p<sub>k</sub> = s*r/norm(r). (Fixes typo in Gentle.) This vector will be a row in the P matrix.
      It is a unit vector in the hypercone defined by the equation p<sub>k</sub>`(I - *Lambda;)p<sub>k</sub> = 0.</li>
  <li>Perform a rank-one update: E &rarr; E - p<sub>k</sub>`*p<sub>k</sub> (fixes typo), and increment k=k+1.
      At each step, E is the projection matrix onto the subspace orthogonal to the rows of P that have been constructed so far.</li>
  <li>If k &lt; d, goto Step 1.</li>
  <li>Generate a <em>d</em>&nbsp;x&nbsp;1 vector, w, of i.i.d. standard normal random variates.<br />
    Form x = E*w and set p<sub>d</sub> = x / norm(x).</li>
  <li>Construct the matrix P by using the vectors p<sub>k</sub> as its rows.<br />
    Return P*diag(Lambda)*P` as the random correlation matrix.</li>
</ol>

<p>
I always say that the main benefit of a matrix language such as MATLAB, R, and SAS IML 
is the ease of converting a high-level pseudo-code description of an algorithm into 
a ready-to-run program. The Appendix to this article contains my implementation of the 
algorithm in a SAS IML moduled called CorrWithEigen_MO.
The next section shows how to use the function to generate a random correlation matrix.
</p>


<h3>Generate random correlation matrices in SAS</h3>
<p>
First, run the program in the Appendix, which stores the CorrWithEigen_MO function.
You can then load the function and use it in other programs.
The following call to PROC IML generates two random 6&nbsp;x&nbsp;6 correlation matrices 
that have the eigenvalues Lambda = {1.8, 1.5, 1, 1, 0.5, 0.2}.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
load module=<span style="color: #66cc66;">&#40;</span>CorrWithEigen_MO<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> randseed<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">123</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">reset</span> <span style="color: #0000ff;">fuzz</span>;   <span style="color: #006400; font-style: italic;">/* print tiny numbers as 0 */</span>
&nbsp;
<span style="color: #006400; font-style: italic;">/* Specify the spectrum for a 6x6 corr matrix */</span>
Lambda = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">1.8</span>, <span style="color: #2e8b57; font-weight: bold;">1.5</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">0.5</span>, <span style="color: #2e8b57; font-weight: bold;">0.2</span><span style="color: #66cc66;">&#125;</span>;
<span style="color: #006400; font-style: italic;">/* get random 6x6 correlation matrix with this spectrum */</span>
Corr1 = CorrWithEigen_MO<span style="color: #66cc66;">&#40;</span> Lambda <span style="color: #66cc66;">&#41;</span>;
<span style="color: #006400; font-style: italic;">/* call again, make sure we get a different answer */</span>
Corr2 = CorrWithEigen_MO<span style="color: #66cc66;">&#40;</span> Lambda <span style="color: #66cc66;">&#41;</span>;
print Corr1<span style="color: #66cc66;">&#91;</span>F=best6.<span style="color: #66cc66;">&#93;</span>, Corr2<span style="color: #66cc66;">&#91;</span>F=best6.<span style="color: #66cc66;">&#93;</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* verify the spectrum of the random matrices */</span>
v1 = eigval<span style="color: #66cc66;">&#40;</span>Corr1<span style="color: #66cc66;">&#41;</span>;
v2 = eigval<span style="color: #66cc66;">&#40;</span>Corr2<span style="color: #66cc66;">&#41;</span>;
print Lambda v1 v2;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO1.png" alt="" width="311" height="200" class="alignnone size-full wp-image-59904" srcset="https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO1.png 311w, https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO1-300x193.png 300w" sizes="(max-width: 311px) 100vw, 311px" />

<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO2.png" alt="" width="311" height="198" class="alignnone size-full wp-image-59901" srcset="https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO2.png 311w, https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO2-300x191.png 300w" sizes="(max-width: 311px) 100vw, 311px" />
<br />
<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/RandCorrMO3.png" alt="" width="131" height="199" class="alignnone size-full wp-image-59898" />

<p>
The output shows two different random 6x6 correlation matrices. 
Each matrix has the same set of eigenvalues, as demonstrated by the 
calls to the EIGVAL function. 
</p>

<h3>Summary</h3>
<p>
This article provides an IML implementation of the 
Marsaglia and Olkin (1984) algorithm for the direct generation of random correlation matrices that have a specified spectrum.
You can use the algorithm to generate random correlation or covariance matrices in SAS.
You can use this function in simulation studies where you need
a positive definite matrix.
</p>


<h3>Appendix: The Marsaglia and Olkin Algorithm in IML</h3>
<p>
This section defines an IML function (and some helper functions) that implement the Marsaglia and Olkin (1984) algorithm for the direct generation of random correlation matrices that have a specified spectrum.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* From Gentle (2003, p. 200) Random Number Generation and Monte Carlo Methods
   Algorithm 5.9 
   Marsaglia-Olkin (1984) Method for Random Correlation Matrices with Given Eigenvalues
   Ref: Marsaglia, G. and Olkin, I. (1984) &quot;Generating Correlation Matrices&quot;, 
        SIAM J. on Sci. and Stat. Comp., 5(2), pp 470-475. doi 10.1137/0905034.
&nbsp;
   INPUT:
   Lambda = the spectrum of a dxd SPD correlation matrix. Lambda is a vector
            (lambda_1, ..., lambda_d), where lambda_i &gt; 0 and \sum lambda_i = d.
   OUTPUT:
   R = dxd correlation matrix, R, such that the eigenvalues of R are the specified spectrum:
        Lambda(R) = (lambda_1, ..., lambda_d)
   ALGORITHM:
   0. Set E = I_d and k=1.
   1. Generate d-vector, w, of i.i.d. std normal deviate.
      Form x = E*w and compute the scalar a = (1-Lambda)` * x##2
   2. Generate a d-vector, z, of i.i.d. std normal variates. 
      Form y = E*z and compute the scalars 
      b = (1-Lambda)` * (x#y)
      c = (1-Lambda)` * y##2
      e^2 = b##2 - a*c
   3. If e^2 &lt; 0, goto Step 2.
   4. Choose a random sign, s \in {-1, 1}. Set r = (b+s*e)/a * x - y.
   5. Choose another random sign, s \in {-1, 1}. Set p_k = s*r/norm(r). (fixes typo in Gentle)
   6. Perform a rank-one update: E -&gt; E - p_k`*p_k (fixes typo), and increment k=k+1.
   7. If k &lt; d, goto Step 1.
   8. Generate a d-vector, w, of i.i.d. std normal deviate.
      Form x = E*w and set p_d = x / norm(x).
   9. Construct the matrix P by using the vectors p_k as its rows.
      Return P*diag(Lambda)*P` as the random correlation matrix.
*/</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* return k random signs with values +1 or -1.
   Let B be a binary random variable chosen uniformly at random from {0,1}.
   Then C = 2*(B - 1/2) is random uniform in {-1,1}.
*/</span>
start RandSign<span style="color: #66cc66;">&#40;</span>k<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #006400; font-style: italic;">*(randfun(k, &quot;Bernoulli&quot;, 0.5) - 0.5) );</span>
finish RandSign;
&nbsp;
<span style="color: #006400; font-style: italic;">/* for column vector v, standardize so sum(v)=nrow(v) */</span>
start StdizeEigenval<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
    L = colvec<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
    <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span> L <span style="color: #006400; font-style: italic;">* nrow(L)/sum(L) );</span>  <span style="color: #006400; font-style: italic;">/* make sum(v)=dimension */</span>
finish StdizeEigenval;
&nbsp;
<span style="color: #006400; font-style: italic;">/* The  Marsaglia-Olkin (1984) method for creating a random correlation 
   matrix with a specified set of eigenvalues */</span>
start CorrWithEigen_MO<span style="color: #66cc66;">&#40;</span>targetLambda, maxIters=<span style="color: #2e8b57; font-weight: bold;">100</span><span style="color: #66cc66;">&#41;</span>;
   lambda = StdizeEigenval<span style="color: #66cc66;">&#40;</span>targetLambda<span style="color: #66cc66;">&#41;</span>;
   d = nrow<span style="color: #66cc66;">&#40;</span>Lambda<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> any<span style="color: #66cc66;">&#40;</span>Lambda &lt; <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>; 
      print <span style="color: #a020f0;">&quot;ERROR: The spectrum must contain only nonnegative values&quot;</span>; 
      <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> J<span style="color: #66cc66;">&#40;</span>d,d,.<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>; 
   <span style="color: #0000ff;">end</span>;
   <span style="color: #006400; font-style: italic;">/* If the spectrum is {1,1,...,1}, the only solution is I(d).
      The do-while logic in the M-O method will enter an infinite loop
      in this case, so detect this edge case and return the identity matrix */</span>
   <span style="color: #0000ff;">if</span> norm<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span> - Lambda, <span style="color: #a020f0;">&quot;LInf&quot;</span><span style="color: #66cc66;">&#41;</span> &lt; 1e-8 <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>I<span style="color: #66cc66;">&#40;</span>d<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* use column vectors for Lambda, w, and z */</span>
   w = j<span style="color: #66cc66;">&#40;</span>d, <span style="color: #2e8b57; font-weight: bold;">1</span>, .<span style="color: #66cc66;">&#41;</span>;
   z = j<span style="color: #66cc66;">&#40;</span>d, <span style="color: #2e8b57; font-weight: bold;">1</span>, .<span style="color: #66cc66;">&#41;</span>;
   P = j<span style="color: #66cc66;">&#40;</span>d, d, .<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #006400; font-style: italic;">/* 0. Set E = I_d and k=1. */</span>
   E = I<span style="color: #66cc66;">&#40;</span>d<span style="color: #66cc66;">&#41;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* compute the k_th row of P */</span>
   <span style="color: #0000ff;">do</span> k = <span style="color: #2e8b57; font-weight: bold;">1</span> to d-<span style="color: #2e8b57; font-weight: bold;">1</span>;
      <span style="color: #006400; font-style: italic;">/* 1. Generate dx1 vector, w, of i.i.d. std normal variates.
            Form x = E*w and compute the scalar a = (1-Lambda)` * x##2. */</span>
      <span style="color: #0000ff;">call</span> randgen<span style="color: #66cc66;">&#40;</span>w, <span style="color: #a020f0;">&quot;Normal&quot;</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">x</span> = E<span style="color: #006400; font-style: italic;">*w;</span>
      a = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-Lambda<span style="color: #66cc66;">&#41;</span> # <span style="color: #0000ff;">x</span>##<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #006400; font-style: italic;">/* 2. Generate a dx1 vector, z, of i.i.d. std normal variates. 
            Form y = E*z and compute the scalars 
            b = (1-Lambda)` * (x#y)
            c = (1-Lambda)` * y##2
            e2 = b##2 - a*c
         3. If e2 &lt; 0, goto Step 2.     */</span>
      e2 = -<span style="color: #2e8b57; font-weight: bold;">1</span>;
      <span style="color: #0000ff;">do</span> cnt = <span style="color: #2e8b57; font-weight: bold;">1</span> to maxIters <span style="color: #0000ff;">until</span><span style="color: #66cc66;">&#40;</span>e2 &gt; <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
         <span style="color: #0000ff;">call</span> randgen<span style="color: #66cc66;">&#40;</span>z, <span style="color: #a020f0;">&quot;Normal&quot;</span><span style="color: #66cc66;">&#41;</span>;
         y = E<span style="color: #006400; font-style: italic;">*z;</span>
         b = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-Lambda<span style="color: #66cc66;">&#41;</span> # <span style="color: #0000ff;">x</span> # y<span style="color: #66cc66;">&#41;</span>;
         c = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-Lambda<span style="color: #66cc66;">&#41;</span> # y##<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
         e2 = b##<span style="color: #2e8b57; font-weight: bold;">2</span> - a<span style="color: #006400; font-style: italic;">*c;</span>
      <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">if</span> e2 &lt;= <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         print <span style="color: #a020f0;">&quot;ERROR:  Marsaglia-Olkin method failed after 100 iterations.&quot;</span>;
         <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>J<span style="color: #66cc66;">&#40;</span>d, d, .<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>; 
      <span style="color: #0000ff;">end</span>;
      <span style="color: #006400; font-style: italic;">/* 4. Choose two random signs, s \in {-1, 1} */</span>
      s = RandSign<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
      r = <span style="color: #66cc66;">&#40;</span>b+s<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*sqrt(e2))/a * x - y;</span>   <span style="color: #006400; font-style: italic;">/* Set r = (b+s*e)/a * x - y (Note Pr(a=0)=0) */</span>
      p_k = s<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> <span style="color: #006400; font-style: italic;">* r/norm(r);</span>            <span style="color: #006400; font-style: italic;">/* 5. Set p_k = s/norm(r) * w */</span>
&nbsp;
      <span style="color: #006400; font-style: italic;">/* 6. Perform a rank-one update: E -&gt; E - r*r` */</span>
      E = E - p_k<span style="color: #006400; font-style: italic;">*p_k`;</span>
      P<span style="color: #66cc66;">&#91;</span>,k<span style="color: #66cc66;">&#93;</span> = p_k;
   <span style="color: #0000ff;">end</span>;   <span style="color: #006400; font-style: italic;">/* 7. If k &lt; d, goto Step 1. */</span>
&nbsp;
   <span style="color: #006400; font-style: italic;">/* 8. Generate a dx1 vector, w, of i.i.d. std normal variates.
         Form x = E*w and set p_d = x / norm(x). */</span>
   <span style="color: #0000ff;">call</span> randgen<span style="color: #66cc66;">&#40;</span>w, <span style="color: #a020f0;">&quot;Normal&quot;</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">x</span> = E<span style="color: #006400; font-style: italic;">*w;</span>
   P<span style="color: #66cc66;">&#91;</span>,d<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">x</span> / norm<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #006400; font-style: italic;">/* 9. Construct the matrix P by using the vectors p_k as its rows.
         Return P*diag(Lambda)*P` as the random correlation matrix.
         (Note: Instead, I will construct P by using p_k as columns, then return P`*Lambda*P) */</span>
   G = P` # <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span>Lambda`<span style="color: #66cc66;">&#41;</span>;
   Corr = G<span style="color: #006400; font-style: italic;">*G`;</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> Corr <span style="color: #66cc66;">&#41;</span>;
finish CorrWithEigen_MO;
store module=<span style="color: #66cc66;">&#40;</span>RandSign StdizeEigenval CorrWithEigen_MO<span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;</pre></td></tr></table></div>


<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html">A direct method to generate correlation matrices with specified eigenvalues</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/08/24/direct-generate-corr.html/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2025/01/spectrumViz4-150x150.png" />
	</item>
		<item>
		<title>A visual introduction to the Genz method for computing multivariate normal probabilities</title>
		<link>https://blogs.sas.com/content/iml/2026/08/17/genz-method.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/08/17/genz-method.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 17 Aug 2026 09:24:18 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Numerical Analysis]]></category>
		<category><![CDATA[Simulation]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59814</guid>

					<description><![CDATA[<p>I've been working on a project that uses quasi-Monte Carlo (QMC) techniques to estimate probabilities for multivariate normal (MVN) distributions on finite or infinite rectangular regions. The goal is to enable SAS users to compute these probabilities accurately and efficiently. My implementation is based on a numerical technique called the [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/17/genz-method.html">A visual introduction to the Genz method for computing multivariate normal probabilities</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
I've been working on a project that uses quasi-Monte Carlo (QMC) techniques to estimate probabilities for multivariate normal (MVN) distributions on finite or infinite rectangular regions. 
The goal is to enable SAS users to compute these probabilities accurately and efficiently.
</p>

<p>
My implementation is based on a numerical technique called the Genz transformation (<a href="https://www.jstor.org/stable/1390838">A. Genz, 1992</a>; <a href="https://link.springer.com/book/10.1007/978-3-642-01689-9">Genz and Bretz, 2009, pp. 49-50</a>). 
It leverages the geometric fact that the Cholesky factor of a correlation matrix provides a transformation that maps uncorrelated MVN variables to correlated MVN variables. I previously wrote about <a href="https://blogs.sas.com/content/iml/2012/02/08/use-the-cholesky-transformation-to-correlate-and-uncorrelate-variables.html">using the Cholesky transformation to correlate variables</a>, and Genz's method is a clever and useful application of this linear algebraic technique. The technique goes back to <a href="https://blogs.sas.com/content/iml/2012/02/15/what-is-mahalanobis-distance.html">multivariate statistical methods developed by Mahalanobis</a> in the 1930s.
</p>

<p>
This article discusses the Genz transformation method and shows how to compute and visualize the integrand for a bivariate normal distribution. This low-dimensional example demonstrates the main ideas that are used in higher dimensions.
</p>

<h3>The computation of MVN probabilities</h3>
<p>
The goal is to estimate the probability that a random multivariate normal vector falls inside a specific rectangular region:
<br />
&nbsp;&nbsp;&nbsp;&nbsp;
<span class='MathJax_Preview'>\( P( L_1 < X_1 < U_1, L_2 < X_2 < U_2, \dots, L_d < X_d < U_d ) \)</span><script type='math/tex'> P( L_1 < X_1 < U_1, L_2 < X_2 < U_2, \dots, L_d < X_d < U_d ) </script>
<br />
where (X<sub>1</sub>, X<sub>2</sub>, ..., X<sub>d</sub>) ~ MVN(0, &Sigma;), and &Sigma; is the covariance matrix. The lower and upper limits are defined by the vectors <em>L</em> = (L<sub>1</sub>, L<sub>2</sub>, ..., L<sub>d</sub>) and <em>U</em> = (U<sub>1</sub>, U<sub>2</sub>, ..., U<sub>d</sub>).
We'll adopt the convention that each L<sub>i</sub> is less than U<sub>i</sub> and we'll allow
elements of <em>L</em> to represent negative infinity and elements of <em>U</em> to represent positive infinity.
In SAS, you can use the special missing value .M to represent minus infinity and .I to represent positive infinity.
</p><p>
The probability is formally computed by evaluating a multidimensional integral:
<br />
<span class='MathJax_Preview'>\(
\int_{L_1}^{U_1} \int_{L_2}^{U_2} \cdots \int_{L_d}^{U_d} \phi(x; \Sigma)\, dx
\)</span><script type='math/tex'>
\int_{L_1}^{U_1} \int_{L_2}^{U_2} \cdots \int_{L_d}^{U_d} \phi(x; \Sigma)\, dx
</script>
where &phi;(<em>x</em>; &Sigma;) is the MVN density function.  
</p>
<p>
Does that integral look scary to you? Because it looks scary to me! 
In numerical analysis, there are not many good ways to solve high-dimensional integrals.
For low to moderate dimensions, you can use ordinary Monte Carlo simulations to estimate this probability. There are 
<a href="https://blogs.sas.com/content/iml/2016/03/14/monte-carlo-estimates-of-pi.html">two common Monte Carlo techniques</a>:
</p>
<ul>
<li><strong>The Region method</strong> (a.k.a., "naive Monte Carlo"): Generate <em>B</em> random variates X<sub>i</sub> ~ MVN(0, &Sigma;) and calculate the proportion of the X<sub>i</sub> inside the rectangular region defined by the limits. For completeness, the Appendix shows a traditional Monte Carlo implementation of the region method.
</li>
<li><strong>The Average Function method:</strong> Evaluate a certain function for <em>B</em> random variates in the domain of the function. 
Compute the average of the values.
I am intentionally leaving "the function" and "the domain" unspecified for now. As we will soon see, Genz transforms the 
domain of the problem, and 
the function that gets evaluated is somewhat complicated.
</li>
</ul>
<p>
For the Average Function method, you can generate quasi-random variates and perform a 
<a href="https://blogs.sas.com/content/iml/2025/11/17/quasi-monte-carlo.html">quasi-Monte Carlo analysis</a>. A 
quasi-Monte Carlo estimate has a smaller standard error than a naive Monte Carlo estimate that uses the same number of points. 
</p><p>
Unfortunately, QMC methods are limited to finite regions because 
quasi-random sequences are generated inside the unit hypercube.
Genz solved this issue by transforming the general MVN region into an equivalent integration problem strictly on the unit hypercube. Because the innermost integral can be evaluated analytically, a <em>d</em>-dimensional probability reduces to an integral over the (<em>d</em>-1)-dimensional hypercube:
<br />
&nbsp;&nbsp;&nbsp;&nbsp;
<span class='MathJax_Preview'>\( \int_{(0,1)^{d-1}} g(w) \, dw \)</span><script type='math/tex'> \int_{(0,1)^{d-1}} g(w) \, dw </script>
<br />
where the integrand, <em>g</em>, depends on the covariance matrix and the limits of integration.
</p>

<p>You can use QMC techniques to efficiently estimate the integral with high accuracy. 
You generate <em>N</em> quasi-random points in the (<em>d</em>-1)-dimensional hypercube, evaluate the function <em>g</em> at each point, 
and take the average, as follows:
<br />
&nbsp;&nbsp;&nbsp;&nbsp;
<span class='MathJax_Preview'>\( p \approx \frac{1}{N} \sum_{i=1}^N g(w_i) \)</span><script type='math/tex'> p \approx \frac{1}{N} \sum_{i=1}^N g(w_i) </script>
</p>

<p>
Of course, there is no such thing as a free lunch, so the Genz integration function, <em>g</em>, is more complicated 
than the standard MVN density function. But Genz's paper was seminal because it shows how to evaluate the integral by 
using linear transformations and the inverse CDF transformation.
</p>

<h3>An overview of the Genz transformation</h3>
<p>
Genz's method consists of three main steps:</p>
<ol>
    <li><strong>The Cholesky transformation:</strong> Transform the problem from correlated variables into independent standard normal variables. This transformation has been known since the 1930s.</li>
    <li><strong>Conditional probability:</strong> When integrating the <em>i</em>_th variable, you must condition the bounds on the exact values drawn for the previous (<em>i</em>-1) dimensions. This step uses conditional probability to update the lower and upper limits of integration for each variable.
</li>
    <li><strong>The inverse CDF Transformation:</strong> You can use <a href="https://blogs.sas.com/content/iml/2021/06/23/probability-integral-transform.html">the inverse CDF transformation</a> 
to map these updated bounds into the unit hypercube.</li>
</ol>

<h3>A 2-D Implementation in SAS IML</h3>
<p>
Although Genz's algorithm works in arbitrary dimensions, 
let's use it to integrate the MVN probability in 2-D. SAS already has <a href="https://blogs.sas.com/content/iml/2023/11/29/bivariate-normal-rectangle.html">functions for evaluating the bivariate normal probability</a>, so we can use those functions to check whether 
we implemented Genz's method correctly.
</p>
<p>
Assume <em>L</em> and <em>U</em> are row vectors. The following SAS IML program defines two helper functions to handle infinite bounds for the CDF("Normal") function. The main function is called <code class="preserve-code-formatting">GenzIntegrand_2D</code>. 
As input parameters, it takes two-element vectors <em>L</em> and <em>U</em> and a 2x2 correlation matrix, <em>R</em>.
It also takes a vector of values, <em>w</em>, in the interval (0,1). 
There are several ways you can use that input argument. I will use it to visualize the 
function by passing in a grid of values on the interval (0,1). However, if you want to average the 
function to estimate the 2-D MVN probability, you would send in a set of quasi-random numbers in (0,1) and average the output.
</p>
<p>
The Genz transformation "integrates out" the innermost variable in the problem. 
As a result, the Genz integrand is a function of one fewer variables than the original problem. 
For a 2-D probability, the Genz integrand is a 1-D function.
For a 3-D probability, the integrand is a function of two variables, and so forth.
</p>
<p>
The following SAS IML program begins with three helper functions. The main function is GenzIntegrand_2D.
The comments in the function indicate the three main steps for transforming the bivariate normal probability into a 1-D function
that can be integrated.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* Helper functions: Extend the CDF function so that 
             / 0 if x = -Infinity
   cdf(x) = {  cdf(x) if x is finite 
             \ 1 if x = +Infinity
*/</span>
start cdf_at_lower<span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> L=. <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #66cc66;">&#41;</span>;    <span style="color: #006400; font-style: italic;">/* = cdf(&quot;Normal&quot;, -Infinity) */</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #0000ff;">cdf</span><span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Normal&quot;</span>, L<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
start cdf_at_upper<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> U=. <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #66cc66;">&#41;</span>;    <span style="color: #006400; font-style: italic;">/* = cdf(&quot;Normal&quot;, +Infinity) */</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #0000ff;">cdf</span><span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Normal&quot;</span>, U<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
<span style="color: #006400; font-style: italic;">/* The Title2_from function sets the TITLE2 statement dynamically at runtime. See 
   https://blogs.sas.com/content/iml/2015/01/14/global-statements-loops.html
  The string looks like
  &quot;rho=0.5; P(. &lt; X1 &lt; 2 &amp; -2 &lt; X2 &lt; 1)&quot; 
*/</span>
<span style="color: #0000ff;">%macro</span> char<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;  choose<span style="color: #66cc66;">&#40;</span>&amp;<span style="color: #0000ff;">x</span>=., <span style="color: #a020f0;">&quot;.&quot;</span>, char<span style="color: #66cc66;">&#40;</span>&amp;<span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>  <span style="color: #0000ff;">%mend</span>;
start Title2_from<span style="color: #66cc66;">&#40;</span>L, U, rho<span style="color: #66cc66;">&#41;</span>;
   rhoStr = cats<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;rho=&quot;</span>,char<span style="color: #66cc66;">&#40;</span>rho<span style="color: #66cc66;">&#41;</span>,<span style="color: #a020f0;">&quot;;&quot;</span><span style="color: #66cc66;">&#41;</span>;
   range1 = cats<span style="color: #66cc66;">&#40;</span>%char<span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>,<span style="color: #a020f0;">&quot;&lt; X1 &lt;&quot;</span>,%char<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
   range2 = cats<span style="color: #66cc66;">&#40;</span>%char<span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>,<span style="color: #a020f0;">&quot;&lt; X2 &lt;&quot;</span>,%char<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
   parmStr = catx<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot; &quot;</span>, rhoStr, <span style="color: #a020f0;">&quot;P(&quot;</span>, range1, <span style="color: #a020f0;">&quot;&amp;&quot;</span>, range2, <span style="color: #a020f0;">&quot;)&quot;</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">call</span> execute<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;title2 '&quot;</span> + parmStr + <span style="color: #a020f0;">&quot;';&quot;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* The Genz method transforms a vector of independent standard normal variables 
   Y ~ MVN(0, I) into the observed correlated variables X~MVN(0, R)
   by using the Cholesky factor, C. In 2-D, relates X to Y via the linear equations
   X1 = C11*Y1
   X2 = C21*Y1 + C22*Y2
*/</span>
start GenzIntegrand_2D<span style="color: #66cc66;">&#40;</span>w, L, U, R<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #006400; font-style: italic;">/* The ROOT function returns an upper triangular matrix. Transpose to get lower triangular */</span>
   C = t<span style="color: #66cc66;">&#40;</span>root<span style="color: #66cc66;">&#40;</span>R<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>; 
&nbsp;
   <span style="color: #006400; font-style: italic;">/* First dimension (outer integral) */</span>
   v1 = <span style="color: #2e8b57; font-weight: bold;">0</span>;            <span style="color: #006400; font-style: italic;">/* (un)conditional mean of X1 */</span>
   c11 = C<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* Standardize limits and calculate probability mass M1 */</span>
   alpha = cdf_at_lower<span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>-v1<span style="color: #66cc66;">&#41;</span>/c11 <span style="color: #66cc66;">&#41;</span>;
   beta  = cdf_at_upper<span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>-v1<span style="color: #66cc66;">&#41;</span>/c11 <span style="color: #66cc66;">&#41;</span>;
   M1 = beta - alpha;   <span style="color: #006400; font-style: italic;">/* 1-D marginal probability */</span>
&nbsp;
   <span style="color: #006400; font-style: italic;">/* The inverse CDF maps each w in (0,1) to normal variable y1.
      Note: y1 is a vector in (-Infinity, Infinity) */</span>
   y1 = quantile<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;Normal&quot;</span>, alpha + w <span style="color: #006400; font-style: italic;">* M1);</span>
&nbsp;
   <span style="color: #006400; font-style: italic;">/* Second dimension (inner integral) */</span>
   c21 = C<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>;
   c22 = C<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* Conditional mean depends on the y1 from the previous step.
      Subtract the conditional mean and standardize limits for inner integral. */</span>
   v2 = c21 <span style="color: #006400; font-style: italic;">* y1;</span>
   alpha = cdf_at_lower<span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>-v2<span style="color: #66cc66;">&#41;</span>/c22 <span style="color: #66cc66;">&#41;</span>;
   beta  = cdf_at_upper<span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>-v2<span style="color: #66cc66;">&#41;</span>/c22 <span style="color: #66cc66;">&#41;</span>;
   M2 = beta - alpha;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* Final integrand: M1 is a scalar, M2 is a vector, so g is a vector. */</span>
   g = M1 <span style="color: #006400; font-style: italic;">* M2;</span>
   <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>g<span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Visualize the 1-D integrand for the following 2-D problem */</span>
rho = <span style="color: #2e8b57; font-weight: bold;">0.5</span>;
R = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>   || rho<span style="color: #66cc66;">&#41;</span> //
    <span style="color: #66cc66;">&#40;</span>rho || <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #66cc66;">&#41;</span>;
L = <span style="color: #66cc66;">&#123;</span>.M  -<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>;
U = <span style="color: #66cc66;">&#123;</span> <span style="color: #2e8b57; font-weight: bold;">1</span>   <span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#125;</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Evaluate and plot the Genz integrand, which is defined on the open interval (0,1) */</span>
w = <span style="color: #66cc66;">&#123;</span>1E-4, 5E-4, <span style="color: #2e8b57; font-weight: bold;">0.001</span>, <span style="color: #2e8b57; font-weight: bold;">0.005</span><span style="color: #66cc66;">&#125;</span> // 
    T<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">do</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0.01</span>, <span style="color: #2e8b57; font-weight: bold;">0.99</span>, <span style="color: #2e8b57; font-weight: bold;">0.01</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>    //
    <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">0.995</span>, <span style="color: #2e8b57; font-weight: bold;">0.999</span>, <span style="color: #2e8b57; font-weight: bold;">0.9995</span>, <span style="color: #2e8b57; font-weight: bold;">0.999</span><span style="color: #66cc66;">&#125;</span>; 
g = GenzIntegrand_2D<span style="color: #66cc66;">&#40;</span>w, L, U, R<span style="color: #66cc66;">&#41;</span>;
&nbsp;
<span style="color: #0000ff;">title</span>  <span style="color: #a020f0;">&quot;Genz Integrand for 2-D MVN&quot;</span>;
<span style="color: #0000ff;">call</span> Title2_from<span style="color: #66cc66;">&#40;</span>L, U, rho<span style="color: #66cc66;">&#41;</span>;
xLabel = <span style="color: #a020f0;">&quot;w (Uniform QMC Coordinate)&quot;</span>;
yLabel = <span style="color: #a020f0;">&quot;g(w) = Probability Mass&quot;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span>w, g<span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">X</span> Y<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">label</span>=xLabel
                  other=cat<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;yaxis grid min=0 label='&quot;</span>,yLabel,<span style="color: #a020f0;">&quot;';&quot;</span><span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg1.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59859" srcset="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg1.png 640w, https://blogs.sas.com/content/iml/files/2026/08/GenzInteg1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>The resulting curve represents the integrand for the transformed integration problem. To find the true MVN probability in the region, 
you integrate the function over the interval (0,1). You can estimate the integral 
by evaluating the function on a 1-D QMC sequence, then taking the average. 
Visually, the probability is the average height of this smooth positive function. 
</p>
<p>
Since this is a bivariate problem, you can use SAS to find that the true probability is about 0.677. 
Notice that this value is the average height of the function on (0, 1).
</p>

<h3>The Genz integrand for uncorrelated variables</h3>
<p>
If the correlation matrix for a MVN distribution is the identity, the problem decomposes into a product of 
univariate marginal probabilities. The Genz integrand for this situation is a straight line. The height of the line is the 
probability value. To see this fact in action, let's use a very small value of rho so that the function will be almost a horizontal line.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* If you use a tiny value for rho, the integrand approaches a constant 
   function. When rho=0 exactly, the integrand is the product of the 
   two marginal probabilities. The function becomes a constant.
*/</span>
rho = <span style="color: #2e8b57; font-weight: bold;">0.01</span>;  <span style="color: #006400; font-style: italic;">/* prob = 0.6882697 for this value of rho */</span>
R = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>   || rho<span style="color: #66cc66;">&#41;</span> //
    <span style="color: #66cc66;">&#40;</span>rho || <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #66cc66;">&#41;</span>;
g = GenzIntegrand_2D<span style="color: #66cc66;">&#40;</span>w, L, U, R<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> Title2_from<span style="color: #66cc66;">&#40;</span>L, U, rho<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span>w, g<span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">X</span> Y<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">label</span>=xLabel
                  other=cat<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;yaxis grid min=0 max=0.75 label='&quot;</span>,yLabel,<span style="color: #a020f0;">&quot;';&quot;</span><span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg2.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59865" srcset="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg2.png 640w, https://blogs.sas.com/content/iml/files/2026/08/GenzInteg2-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>


<p>
As expected, the Genz integrand is very close to a horizontal line.
The height of the curve is very close to 0.688, which is the probability value for this problem.
The curve becomes a horizontal line for rho=0.
</p>


<h3>The Genz integrand for small probabilities</h3>
<p>
One of the problems with a Monte Carlo simulation that uses the "Region method" is that the 
method requires many random variates to get an accurate estimate.
For example, if you set rho=-0.5, the bivariate normal probability 
P( X1 &lt; -1 &amp; -3 &lt; X2 &lt; -2 ) = 0.000145.
If you use the Monte Carlo "Region method" to estimate the probability, you would expect only 
145 points per million to be inside the region of interest. Most variates are "wasted" because they are outside of the region. 
In contrast, the Genz transformation of this problem does not waste any evaluations. The Genz integrand is very short, but every 
value in (0,1) contributes to the average value of <em>g</em> on (0, 1). This is shown in the following program:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* What does the integrand look like if we use a negative rho
   and specify a region in which the probability is small?
*/</span>
rho = -<span style="color: #2e8b57; font-weight: bold;">0.5</span>;
R = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>   || rho<span style="color: #66cc66;">&#41;</span> //
    <span style="color: #66cc66;">&#40;</span>rho || <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #66cc66;">&#41;</span>;
L = <span style="color: #66cc66;">&#123;</span>.M  -<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#125;</span>;
U = <span style="color: #66cc66;">&#123;</span>-<span style="color: #2e8b57; font-weight: bold;">1</span>  -<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#125;</span>;
g = GenzIntegrand_2D<span style="color: #66cc66;">&#40;</span>w, L, U, R<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> Title2_from<span style="color: #66cc66;">&#40;</span>L, U, rho<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span>w, g<span style="color: #66cc66;">&#41;</span> grid=<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">X</span> Y<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">label</span>=xLabel
                  other=cat<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;yaxis grid min=0 label='&quot;</span>,yLabel,<span style="color: #a020f0;">&quot;';&quot;</span><span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg3.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59862" srcset="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg3.png 640w, https://blogs.sas.com/content/iml/files/2026/08/GenzInteg3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>


<p>
As expected, the integrand is very small. The maximum value of the function on (0, 1) is approximately 0.0003.
The function is approximately linear and g(0) &asymp; 0, so a quick back-of-the-envelope calculation of the probability is 0.00015, which is pretty doggone 
close to the true value!  You could estimate the probability by using only a few thousand quasi-random points in (0,1), which is a huge savings
over the millions of points that are required for a naive Monte Carlo estimate.
</p>


<h3>Summary</h3>
<p>
This article presents a general computational framework for estimating a probability for a rectangular region 
of a multivariate normal distribution.
Three bivariate examples are given: a problem where the variables are moderately correlated, a problem where 
the variables are almost independent, and an example where the probability is very small. 
For each case, I visualize the Genz integrand, which is a 1-D function. By integrating the Genz integrand, you obtain the 
desired probability. You can use quasi-Monte Carlo integration, which amounts to evaluating the integrand many times
and taking the average.
</p><p>
The algorithm generalizes to higher dimensions. For a <em>d</em>-dimensional MVN distribution, the Genz integrand is a function of <em>d</em>-1 variables that each have (0, 1) as a domain. Thus, the problem reduces to estimating the integral of a function on the unit hypercube, which can be computed efficiently by using quasi-Monte Carlo methods.
</p>


<h3>Appendix: The traditional Monte Carlo estimate</h3>
<p>
As mentioned in the article, the Genz transformation is more efficient than the traditional Monte Carlo estimates.
For example, the following PROC IML program simulates N=1000 points from the bivariate normal distribution with &rho; = 0.5 
that is shown in the first graph.  For this run, using 1000 data points results in an estimated probability of 0.664. 
The true probability of 0.677, so the estimate is not bad. However, the standard error of the Monte Carlo method is 
proportional to 1/sqrt(N), whereas the Genz transformation and quasi-random integration has a much smaller standard error 
proportional to 1/N.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
rho = <span style="color: #2e8b57; font-weight: bold;">0.5</span>;
R = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>   || rho<span style="color: #66cc66;">&#41;</span> //
    <span style="color: #66cc66;">&#40;</span>rho || <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #66cc66;">&#41;</span>;
L = <span style="color: #66cc66;">&#123;</span>.M  -<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>;
U = <span style="color: #66cc66;">&#123;</span> <span style="color: #2e8b57; font-weight: bold;">1</span>   <span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#125;</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Visualize the Monte Carlo estimate P(X1&lt; 1 &amp;&amp; -1 &lt; X2 &lt; 2) */</span>
<span style="color: #0000ff;">N</span> = <span style="color: #2e8b57; font-weight: bold;">1000</span>;
<span style="color: #0000ff;">call</span> randseed<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">123</span>, <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">X</span> = randnormal<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">N</span>, <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#125;</span>, R<span style="color: #66cc66;">&#41;</span>;
hit = <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">X</span><span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span> &lt; U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span> &amp;&amp; <span style="color: #66cc66;">&#40;</span>L<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> &lt; <span style="color: #0000ff;">X</span><span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> &amp;&amp; <span style="color: #0000ff;">X</span><span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> &lt; U<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
p_est = <span style="color: #0000ff;">mean</span><span style="color: #66cc66;">&#40;</span>hit<span style="color: #66cc66;">&#41;</span>;
print p_est;
&nbsp;
result = <span style="color: #0000ff;">X</span>||hit;
<span style="color: #0000ff;">create</span> MCViz <span style="color: #0000ff;">from</span> result<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'x1'</span> <span style="color: #a020f0;">'x2'</span> <span style="color: #a020f0;">'inRegion'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span>;
append <span style="color: #0000ff;">from</span> result;
<span style="color: #0000ff;">close</span>;
<span style="color: #000080; font-weight: bold;">quit</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Area Method Monte Carlo Computation&quot;</span>;
title2 <span style="color: #a020f0;">&quot;Estimated Probability = 0.664; N = 1000&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=MCViz;
   scatter <span style="color: #0000ff;">x</span>=x1 y=x2 / <span style="color: #0000ff;">group</span>=inRegion;
   refline <span style="color: #2e8b57; font-weight: bold;">1</span> / axis=<span style="color: #0000ff;">x</span>;
   refline -<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">2</span> / axis=y;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg4.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59961" srcset="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg4.png 640w, https://blogs.sas.com/content/iml/files/2026/08/GenzInteg4-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/17/genz-method.html">A visual introduction to the Genz method for computing multivariate normal probabilities</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/08/17/genz-method.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/08/GenzInteg1-150x150.png" />
	</item>
		<item>
		<title>Constructing orthogonal vectors in SAS</title>
		<link>https://blogs.sas.com/content/iml/2026/08/10/orthogonal-vectors.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/08/10/orthogonal-vectors.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 10 Aug 2026 09:29:54 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Matrix Computations]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59712</guid>

					<description><![CDATA[<p>One of the great algorithms of linear algebra is the Gram-Schmidt orthogonalization process, which enables you to construct an orthogonal basis for a linear subspace from any set of linearly independent vectors that span the subspace. The Gram-Schmidt process is the basis for the QR decomposition in numerical linear algebra, [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/10/orthogonal-vectors.html">Constructing orthogonal vectors in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
One of the great algorithms of linear algebra is <a href="https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process">the Gram-Schmidt orthogonalization process</a>, which enables you to construct an orthogonal basis for a linear subspace from any set of linearly independent vectors that span the subspace.
The Gram-Schmidt process is the basis for the QR decomposition in numerical linear algebra, which decomposes any matrix, A, into a
product, A = QR, where Q is a matrix with orthonormal columns, and R is an upper triangular matrix.
In statistics, <a href="https://blogs.sas.com/content/iml/2021/07/12/qr-least-squares.html">the QR algorithm can be used for linear least-squares regression</a>, among other uses. 
</p><p>
The ideas behind the orthogonalization process are simple: Project a vector onto a linear subspace and compute the residual vector. The residual vector is orthogonal to the subspace. Thus, 
these two operations decompose any vector into two parts: A vector that lies in the subspace and a vector that is orthogonal to the subspace.
This article shows two subroutines in the SAS IML language that are useful for projecting a vector into a subspace and 
finding an orthogonal vector to a subspace. <a href="https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/imlug/imlug_langref_sect183.htm">The GSORTH subroutine</a> implements the <strong>G</strong>ram-<strong>S</strong>chmidt <strong>orth</strong>ogonalization of a matrix, whereas <a href="https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/imlug/imlug_langref_sect319.htm">the ORTVEC subroutine</a> performs one step
of the process and is useful for finding a <strong>orth</strong>ogonal <strong>vec</strong>tor to a subspace.
</p>

<h3>The Gram-Schmidt orthogonalization of a matrix</h3>
<p>
Suppose V1, V2, ..., Vk are k linearly independent vectors. Let S = span(V1, V2, ..., Vk) be the linear subspace that they span.
The Gram-Schmidt process uses these vectors to construct a set of
orthogonal vectors for S.  In SAS IML, you can put the vectors into the columns of a matrix and call the GSORTH subroutine.
The subroutine takes one input argument (a matrix, V) and returns three output matrices. The output are as follows:
</p>
<ol>
<li>Q: An orthonormal matrix whose columns contain an orthonormal basis for the subspace, S . </li>
<li>T: An upper triangular matrix that transforms the orthonormal basis into the original basis: V = Q*T. </li>
<li>lindep: A binary scalar value. If lindep=1, the columns of V are linearly dependent. If lindep=0, the columns of V are 
linearly independent, which implies dim(S) = k. </li>
</ol>

<p>Here's an example. The columns of V span a 3-D linear subspace in the R<sup>4</sup> Euclidean space.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* the columns of V span a 3-D subspace in R^4 */</span>
V = <span style="color: #66cc66;">&#123;</span> <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0</span>,
     -<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">0</span>  <span style="color: #2e8b57; font-weight: bold;">2</span>,
     -<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">1</span> -<span style="color: #2e8b57; font-weight: bold;">1</span>,
      <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #66cc66;">&#125;</span>;
&nbsp;
<span style="color: #0000ff;">call</span> gsorth<span style="color: #66cc66;">&#40;</span>Q, T, lindep, V<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* output: Q, T, and lindep from the input matrix, V */</span>
print Q<span style="color: #66cc66;">&#91;</span>F=BestD6.<span style="color: #66cc66;">&#93;</span>, T<span style="color: #66cc66;">&#91;</span>F=BestD6.<span style="color: #66cc66;">&#93;</span>;
<span style="color: #006400; font-style: italic;">/* verify that V = Q*T by computing the difference V - Q*T */</span>
maxDiff = <span style="color: #0000ff;">max</span><span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">abs</span><span style="color: #66cc66;">&#40;</span>V - Q<span style="color: #006400; font-style: italic;">*T));</span>
print maxDiff;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/ortvec1.png" alt="" width="154" height="341" class="alignnone size-full wp-image-59754" srcset="https://blogs.sas.com/content/iml/files/2026/08/ortvec1.png 154w, https://blogs.sas.com/content/iml/files/2026/08/ortvec1-135x300.png 135w" sizes="(max-width: 154px) 100vw, 154px" />

<p>
The original three basis vectors are V1={1, -1, -1, 1}, V2={1, 0, 1, 1}, and V3={0, 2, -1, 2}.
They span a subspace, S, in R<sup>4</sup>.
After calling the GSORTH routine, the columns of the matrix Q contain an orthogonal set of 
basis vectors that also span S.  (To standardize the output, the columns of Q have unit norm, 
which means they form an orthonormal basis.)
The T matrix maps one set of basis vectors onto the other. 
Specifically, V = Q*T is the equation that represents each column of V as a linear combination of the columns of Q.
For this example, the columns of V are linearly independent, so lindep=0 and dim(S) = 3.
</p>
<p>
Notice that this Q matrix is not square, so it cannot be called an <a href="https://en.wikipedia.org/wiki/Orthogonal_matrix">orthogonal matrix</a>. 
Instead, a rectangular matrix with orthonormal columns is called a
<a href="https://en.wikipedia.org/wiki/Orthogonal_matrix#Rectangular_matrices">semi-orthogonal matrix</a>.
If, in addition, the columns have unit length, it is called a semi-orthonormal matrix.
</p>

<h3>Computing orthogonal vectors to a linear subspace: The manual way</h3>
<p>
The GSORTH subroutine enables you to compute an orthonormal basis with a single call. In linear algebra texts, the Q matrix
is usually constructed sequentially. At each step, you create a new orthonormal basis vector from one of the original spanning vectors.
To understand how the GSORTH and ORTVEC subroutines work, it helps to look at the manual calculations. 
In my linear algebra class, the Gram-Schmidt orthogonalization process was used so often 
that it is indelibly etched into my brain from performing endless calculations by hand!
This section shows the tedious manual computations. The subsequent section uses the ORTVEC subroutine to simplify these computations.
</p>
<p>
The Gram-Schmidt process builds an orthogonal basis incrementally. Let's look at the SAS IML code.
The original vectors are V<sub>1</sub>, V<sub>2</sub>, and V<sub>3</sub>, which are columns of a matrix, V. 
We use them to construct a new set of orthogonal vectors (U<sub>1</sub>, U<sub>2</sub>, and U<sub>3</sub>), 
which are columns of a semi-orthogonal matrix. 
</p>
<p>
The process works step-by-step, projecting vectors onto the existing subspace and calculating the residual:
</p>

<ul>

  <li>
    <b>Step 1:</b> The first orthogonal vector, U<sub>1</sub>,  is simply the first original vector. 
<br />
    U<sub>1</sub> = V<sub>1</sub>
<br />

<a href="https://blogs.sas.com/content/iml/files/2026/08/ortvec4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/ortvec4.png" alt="" width="200" height="118" class="alignright size-full wp-image-59802" /></a>

If you want to, you can standardize U<sub>1</sub> construct an orthonormal basis, but let's keep it simple for now.
We'll standardize the U vectors at the end.
  </li>

  <li>
    <b>Step 2:</b> The second orthogonal vector, U<sub>2</sub>, is the second original vector minus its projection onto the first orthogonal vector. Geometrically, you decompose V<sub>2</sub> into two components, one lying in the span of U<sub>1</sub> and the other orthogonal to it.
The orthogonal component becomes U<sub>2</sub>:
    <br />
    U<sub>2</sub> = V<sub>2</sub> - proj(V<sub>2</sub>, U<sub>1</sub>)
  </li>

<a href="https://blogs.sas.com/content/iml/files/2026/08/ortvec5.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/ortvec5.png" alt="" width="350" height="120" class="alignright size-full wp-image-59799" /></a>

  <li>
    <b>Step 3:</b> The third orthogonal vector is the third original vector minus its projections onto the first two orthogonal vectors.
Geometrically, you decompose V<sub>3</sub> into two components, one lying in span(U<sub>1</sub>, U<sub>2</sub>) and the other orthogonal to it.
The orthogonal component becomes U<sub>3</sub>:
    <br />
    U<sub>3</sub> = V<sub>3</sub> - proj(V<sub>3</sub>, U<sub>1</sub>) - proj(V<sub>3</sub>, U<sub>2</sub>)
  </li>
</ul>

<p>
In these equations, the projection operator <code class="preserve-code-formatting">proj(y, x)</code> calculates the projection of a vector <em>y</em> onto the line spanned by vector <em>x</em>. 
Let's implement these three steps of the G-S orthogonalization process in the SAS IML language.
To make sure we get the same answer as the GSORTH subroutine, we can standardize the columns of U to form Q:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* manual operation: project y onto span(x) */</span>
start proj_vec<span style="color: #66cc66;">&#40;</span>y, <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   z = <span style="color: #0000ff;">x</span>/ norm<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;        <span style="color: #006400; font-style: italic;">/* unit vector in direction of x */</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #66cc66;">&#40;</span>y`<span style="color: #006400; font-style: italic;">*z) * z );</span>
finish;
&nbsp;
U = j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span> = V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span>;
U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> = V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> - proj_vec<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>, U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span> = V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span> - proj_vec<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span>, U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span> - proj_vec<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span>, U<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
print U;
&nbsp;
<span style="color: #006400; font-style: italic;">/* optionally normalize the columns of U at each step or at the end. */</span>
norm_vec = j<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, ncol<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
Q = j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">do</span> j = <span style="color: #2e8b57; font-weight: bold;">1</span> to ncol<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#41;</span>;
   norm_vec<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span> = norm<span style="color: #66cc66;">&#40;</span>U<span style="color: #66cc66;">&#91;</span>,j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
   Q<span style="color: #66cc66;">&#91;</span>,j<span style="color: #66cc66;">&#93;</span> = U<span style="color: #66cc66;">&#91;</span>,j<span style="color: #66cc66;">&#93;</span> / norm_vec<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span>;
<span style="color: #0000ff;">end</span>;
print norm_vec<span style="color: #66cc66;">&#91;</span>F=Best5.<span style="color: #66cc66;">&#93;</span>, Q<span style="color: #66cc66;">&#91;</span>F=BestD6.<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/ortvec3.png" alt="" width="143" height="367" class="alignnone size-full wp-image-59787" srcset="https://blogs.sas.com/content/iml/files/2026/08/ortvec3.png 143w, https://blogs.sas.com/content/iml/files/2026/08/ortvec3-117x300.png 117w" sizes="(max-width: 143px) 100vw, 143px" />

<p>
Notice that the Q matrix from these steps is identical to the Q matrix that is produced by the GSORTH subroutine.
The U matrix is an "un-standardized" version of Q.
</p>

<h3>Computing orthogonal vectors to a linear subspace: The easier way</h3>
<p>
As described in the previous section, the Gram-Schmidt process is a series of operations that projects vectors onto a linear subspace, 
computes a vector orthogonal to the subspace, and then iterates. 
<a href="https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/imlug/imlug_langref_sect319.htm">The ORTVEC subroutine</a> 
encapsulates one step in this iterative process.
It standardizes the orthogonal vectors at each step, thus forming the Q matrix directly instead of first forming U and then standardizing the columns.
</p><p>
The syntax for the ORTVEC routine is a little complicated. Let <em>n</em> be the dimension of the vectors (in our example, n=4).
The input arguments for the ORTVEC subroutine are an <em>n</em>&nbsp;x&nbsp;1 vector, v, and a semi-orthonormal <em>n</em>&nbsp;x&nbsp;<em>k</em> matrix, Q, where <em>k</em> &le; <em>n</em>.
This syntax is used for finding the (k+1)th orthonormal basis vector, based on the previous k orthonormal vectors.
The vector v is the (k+1)th vector in the original basis.
The ORTVEC routine returns four values: two vectors, w and r, a scalar rho, and a binary flag that tells you whether v is in the span of the columns of Q. The return values provide the decomposition of v into a component inside span(Q) and a component orthogonal to span(Q). In symbols,
v = Q*r + rho*w, where norm(w)=1.
The vector w is orthogonal to the span of the columns of Q.
The vector w is the most important output from the ORTVEC subroutine. 
</p>
<p>
To get the first orthonormal basis vector, you omit the Q matrix.
This is shown in the following example, which uses the ORTVEC subroutine to compute the same Q vector as in the previous section:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* the ORTVEC function in SAS IML performs several linear algebra operations
   related to G-S orthogonalization */</span>
Q= j<span style="color: #66cc66;">&#40;</span>nrow<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#41;</span>, ncol<span style="color: #66cc66;">&#40;</span>V<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> ortvec<span style="color: #66cc66;">&#40;</span>w1,r,rho1,lindep, V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* Step 1: No Q matrix yet; rho = norm(v1); v1 = rho1*w1 */</span>
Q<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span> = w1;
<span style="color: #006400; font-style: italic;">/* Check: v1 = rho1*w1; print (v1-V[,1]); */</span>
&nbsp;
<span style="color: #0000ff;">call</span> ortvec<span style="color: #66cc66;">&#40;</span>w2,r2,rho2,lindep, V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span>, Q<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* Step 2: v2 = Q1*r2 + rho2*w2 */</span>
Q<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span> = w2;
<span style="color: #006400; font-style: italic;">/* Check: v2 = Q[,1]*r2 + rho2*w2; print (v2-V[,2]); */</span>
&nbsp;
<span style="color: #0000ff;">call</span> ortvec<span style="color: #66cc66;">&#40;</span>w3,r3,rho3,lindep, V<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span>, Q<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span>:<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* Step 3: v3 = [Q1 Q2]*r3 + rho3*w3 */</span>
Q<span style="color: #66cc66;">&#91;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#93;</span> = w3;
<span style="color: #006400; font-style: italic;">/* Check: v3 = Q[,1:2]*r3 + rho3*w3; print (v3 - V[,3]); */</span>
print Q;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/ortvec2.png" alt="" width="149" height="149" class="alignnone size-full wp-image-59769" />

<p>
You now have three equivalent ways to construct the Q matrix. 
If you are interested in a programming challenge, you could put these ORTVEC operations into a DO loop (or a function) that performs 
the entire Gram-Schmidt orthogonalization algorithm
for an arbitrary matrix, V. 
For an extra challenge, print out the sequence of 'r' vectors and the 'rho' scalars. 
Can you determine how they are related to the matrix T that is provided by the GSORTH routine?
</p>

<h3>Summary</h3>
<p>
The Gram-Schmidt orthogonalization algorithm enables you to construct an orthogonal basis for the span of other linearly independent vectors.
The orthogonalization process decomposes every vector into 
a component that lies inside a subspace and a vector that is orthogonal to the subspace.
In the SAS IML language, you can use the GSORTH subroutine to perform the full algorithm, or you can use 
the ORTVEC subroutine to perform one step.
There are many applications that require finding a vector that is orthogonal to a subspace, so 
the ORTVEC subroutine is useful in these applications.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/10/orthogonal-vectors.html">Constructing orthogonal vectors in SAS</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/08/10/orthogonal-vectors.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2020/12/ProjRSq1-150x150.png" />
	</item>
		<item>
		<title>Nonnegative matrix factorization with sparseness constraints</title>
		<link>https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html#comments</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 03 Aug 2026 09:21:23 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Data Analysis]]></category>
		<category><![CDATA[Matrix Computations]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59601</guid>

					<description><![CDATA[<p>A previous article describes the nonnegative matrix factorization (NMF). You can use the NMF to reveal important features in data that can be used to reduce the dimensionality of the problem. NMF is useful when the data are nonnegative, such as counts or pixel values in an image. The goal [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html">Nonnegative matrix factorization with sparseness constraints</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
A previous article describes <a href="https://blogs.sas.com/content/iml/2026/03/30/nmf-whisky.html">the nonnegative matrix factorization (NMF)</a>. 
You can use the NMF to reveal important features in 
data that can be used to reduce the dimensionality of the problem. NMF is useful when the data are nonnegative, such as counts or pixel values in an image.
The goal of NMF is to find a small number of new nonnegative variables that are interpretable and such that 
each original observation is approximated as a nonnegative linear combination of these new variables. In that sense, NMF shares similarities with principal component analysis (PCA), but the PCA factors and the coefficients contain negative values, which hampers interpretability.
</p>
<p>
Recently, I read a paper by Patrick Hoyer (<a href="https://www.jmlr.org/papers/volume5/hoyer04a/hoyer04a.pdf">2004, "Non-negative matrix factorization with sparseness constraints"</a>), that imposes constraints on the NMF method. The constraints are designed to make the factors more sparse, which is why Hoyer introduced his <a href="https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html">sparseness measure for vectors</a>. Briefly, Hoyer's algorithm results in nonnegative factors that might be more interpretable. 
</p><p>
This article shows an example by revisiting the Scotch whisky data, which I analyzed by using NMF in a previous article.
On paper, Hoyer's idea seemed both interesting and powerful. However, after running a sparseness-constrained NMF on an example, I was not 
impressed.
Read on to find out why. 
To try out the Hoyer algorithm yourself, 
<a href="https://github.com/sascommunities/the-do-loop-blog/blob/master/NMF/NMF_sparse.sas">download the SAS program that 
implements a sparseness-constrained NMF factorization</a>.

</p>

<h3>An unconstrained feature set for Scotch whiskies</h3>
<p>
<a href="https://blogs.sas.com/content/iml/2026/03/30/nmf-whisky.html">The previous article</a> 
used the NMF to extract a four-dimensional basis of vectors for Scotch whisky data.
It is known that whiskies often have regional characteristics, such as the
"earthy" or "peaty" whiskies from the island of Islay. Ideally, the NMF features would reflect these regional flavor 
characteristics.
</p><p>
The whisky data can be represented as an
86x12 nonnegative data matrix, X.
Each row represents flavor characteristics of a whisky.
Each whisky is assigned a flavor profile that consists of the values 0-4 for the 12 flavors.
The 12 flavors are Tobacco, Medicinal, Smoky, Body, Spicy, Winey, Nutty, Honey, Malty, Fruity, Sweetness, and Floral.

</p><p>
The NMF creates a rank-k approximation to X. This article uses k=4.
Thus, X is approximated as a product W*H,
where W is 86x4 and H is 4x12.
The rows of H are the "flavor profile" feature vectors.
They form a basis for a 4-D linear subspace.
They are analogous to the principal components (eigenvectors) 
in a PCA analysis.
Each row of the W matrix is a set of weights (or coefficients) for the 4-factor approximation of the corresponding row of X. 
</p><p>
The following image shows the transposed H matrix. The columns in the image visualize a 4-D basis for the unconstrained NMF. 
</p>

<a href="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse1.png" alt="" width="360" height="480" class="alignnone size-full wp-image-59649" srcset="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse1.png 360w, https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse1-225x300.png 225w" sizes="(max-width: 360px) 100vw, 360px" /></a>


<p>The columns of H` are the features found by the NMF. The NMF method is not unique, but in this image:
</p>
<ol>
<li>The first NMF factor is a combination of six flavors: Smoky, Spicy, Malty, Fruity, Sweetness, and Floral variables.
</li>
<li>The second NMF factor is a combination of 10 flavors. 
</li>
<li>The third factor is primarily composed of the Tobacco, Medicinal, Smoky, and Body flavors.
</li>
<li>The fourth factor is primarily composed of Nutty flavor, with smaller amounts of Malty and Sweetness.
</li>
</ol>

<p>
In terms of <a href="https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html">Hoyer's sparseness measure</a>, the first and second features are not sparse. Hoyer's measure ranges from 0 (not at all sparse) to 1 (very sparse). The following table shows that 
the first NMF feature has moderate sparseness (0.459). 
The second feature has very little sparseness (0.148). 
The third and fourth features are strongly sparse (0.601) and very strongly sparse (0.822), respectively.
</p>

<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse2.png" alt="" width="215" height="66" class="alignnone size-full wp-image-59646" />

<p>
Before leaving the visualization of the unconstrained NMF, I want to draw your attention to the first row of H`. The third factor contains a small amount of the Tobacco variable. The cell is light orange. If you print out the H matrix, you can see that the first and second factor also contain a trace amount of the tobacco variable. However, compared to the other variables, this rank-4 NMF approximation does not represent the tobacco variable well. It seems that the tobacco variable probably points in a direction that is primarily orthogonal to the span of these four NMF factors.
</p>

<h3>A sparseness-constrained NMF algorithm</h3>
<p>
There are 12 variables and k=4 factors, so it would be nice if features were combinations of no more than four or five variables. 
If a 12-D vector has 4 nonzero components and 8 zero components, Hoyer's sparseness measure is about 0.6.
Let's use that value as a target value. 
What will the value 0.6 do?
The unconstrained factors naturally ranged from 0.15 to 0.82. 
The sparseness constraint forces the dense factor (0.15) to become much sparser.
However, it will actually <em>reduce</em> the sparseness of the fourth factor!
</p><p>

I implemented Hoyer's sparseness-constrained NMF in SAS IML. The following image shows the result of running this constrained NMF algorithm on the Whisky data and constraining the rows of the H matrix to have sparseness equal to 0.6:
</p>


<a href="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse3.png" alt="" width="360" height="480" class="alignnone size-full wp-image-59643" srcset="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse3.png 360w, https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse3-225x300.png 225w" sizes="(max-width: 360px) 100vw, 360px" /></a>


<p>
In the new NMF factorization, the sparseness of EVERY feature (rows of the H matrix) is exactly 0.6. 
In the new factorization, all features depend on 4-5 variables. 
Notice that the second feature, which used to be a blend of 10 flavors, is very different. 
Some of the flavors that were previously captured in the second feature are now part of other factors. 
For example, the Winey contributions have moved to the fourth factor,
and the Honey contribution has moved to the first factor.
</p><p>
If you look closely at the first row in the image, you'll see that 
no factor contains the Tobacco variable!
In this rank-4 sparseness-constrained factorization, the sparseness constraint forces the columns of H` to 
be formed by using only a few variables. The Tobacco variable is not included in any sparse factor.
Thus, this model predicts that no Scotch whisky has a Tobacco flavor. 
</p>
<p>
If you want to include
the Tobacco variable in the NMF factors, you need to relax
the sparseness constraint (or use a higher-rank approximation). For example, setting the sparseness target
to 0.5 will result in factors that include Tobacco, but the resulting factors each contain six or more variables
and are not as interpretable.
</p><p>
This is a specific instance of a general truth: The unconstrained NMF factorization will always 
approximate the data matrix better than a sparseness-constrained factorization. 
Sometimes, the lack of fit might be qualitatively noticeable, as in this example
where the Tobacco variable is not included in any NMF factor.
</p>

<h3>The tradeoffs between sparseness and goodness of fit</h3>
<p>
The sparseness-constrained NMF is always a poorer approximation to the data matrix. Thus, you have to decide
whether the trade-offs are worth it: 
</p>
<ul>
<li>Is it better to use the unconstrained NMF? The fit is better, but some factors might 
be formed as a linear combination of many variables, which hampers interpretability. 
</li>
<li>Is it better to use the sparseness-constrained NMF? The fit is worse, but you can force the factors 
to be sparse and therefore highly interpretable. On the other hand, the sparseness constrained NMF
is a <a href="https://en.wikipedia.org/wiki/Procrustes">Procrustean solution</a> to the interpretability
of the factors. By forcing every factor to have the same sparseness, it ignores 
the natural data-induced correlations between variables. 
</li>
</ul>
<p>
My general preference is to let the data decide how many variables are
contained in each NMF factor. The unconstrained NMF approximation seems more faithful to the data.
The factors in the unconstrained NMF are mostly interpretable. Their sparseness ranges from 
0.148 (not sparse) to 0.822 (strongly sparse).
It isn't terrible to have a factor whose 
interpretation is "an overall blend of flavors." After all, every observation is a Scotch whisky,
so it isn't surprising to have a common factor that describes flavors that are common to many whiskies.
</p><p>
In the sparseness-constrained algorithm, every factor has exactly the same sparseness.
Every factor depends strongly on four or five flavors.
This does not seem realistic. 
If regional distilleries produce whiskies that have one or two special flavor characteristics, 
it seems better to have factors that are linear combinations of only one or two flavors.
But the sparseness of such a factor would be too high for the specified constraint.
</p>

<h3>Summary</h3>
<p>
In multivariate statistics, the best algorithms are often those that let the data decide the features.
By adding a sparseness constraint to the NMF algorithm,
we let <a href="https://www.merriam-webster.com/dictionary/the%20tail%20wagging%20the%20dog">the tail wag the dog</a>.
The sparseness essentially dictates how many variables are important in each factor.
If the sparseness value is high, then every factor will contain a small number of important variables,
which might not be appropriate for the data. 
</p><p>
There is also the mathematical fact that a constrained solution will always have a poorer fit than an unconstrained solution.
By itself, that doesn't bother me too much, but you should get something valuable for the loss of fit. 
In a sparseness-constrained NMF, it isn't clear that the benefits offset the poorer approximation to the data.
</p>


<h3>Appendix: Sparseness and goodness of fit</h3>
<p>
It is an interesting exercise to plot the fit of rank-4 NMF approximation as you let the sparseness constraint vary from small to large values.
The following image summarizes that experiment for the Whisky data.
I have overlaid a few plots of the H` matrix so that you can see the effect that sparseness has on the NMF factors.
The horizontal line is the relative error of the unconstrained NMF.  The relative error 
compares the NMF error E=||X - W*H||<sub>F</sub> with the smallest possible error, which is produced by using a rank-k SVD.
</p>

<a href="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4.png" alt="" width="653" height="415" class="alignnone size-full wp-image-59679" srcset="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4.png 1088w, https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4-300x191.png 300w, https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4-1024x651.png 1024w, https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse4-768x488.png 768w" sizes="(max-width: 653px) 100vw, 653px" /></a>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html">Nonnegative matrix factorization with sparseness constraints</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/08/NMF_sparse3-150x150.png" />
	</item>
		<item>
		<title>Hoyer&#039;s sparseness measure</title>
		<link>https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html#comments</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 27 Jul 2026 09:26:57 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Data Analysis]]></category>
		<category><![CDATA[Matrix Computations]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59489</guid>

					<description><![CDATA[<p>In his 2004 paper, "Non-negative Matrix Factorization with Sparseness Constraints," Patrick Hoyer introduced a function that measures the sparseness of a nonzero vector. The paper does not explain or motivate the formula, so this article describes the geometry and intuition behind Hoyer's formula, along with a visualization and examples. Hoyer's [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html">Hoyer&#039;s sparseness measure</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
In his 2004 paper, <a href="https://www.jmlr.org/papers/volume5/hoyer04a/hoyer04a.pdf">"Non-negative Matrix Factorization with Sparseness Constraints,"</a> Patrick Hoyer introduced a function that measures the sparseness of a nonzero vector.
The paper does not explain or motivate the formula, so this article 
describes the geometry and intuition behind Hoyer's formula, along with a visualization and examples.
Hoyer's sparseness measure has been used to analyze matrix factorizations and to interpret spectra in mass spectrometry.
</p>

<h3>Sparseness versus sparsity</h3>
<p>
First, I want to clarify the difference between Hoyer's definition of sparseness (for a vector) and the traditional
notion of sparsity (for a matrix).
</p><p>
In numerical analysis, <a href="https://blogs.sas.com/content/iml/2020/06/22/visualize-structure-sparse-matrix.html">a matrix is said to be <em>sparse</em> if most of its elements are exactly zero</a> and only a few elements are nonzero. For example, a large 
diagonal (or tridiagonal matrix) is sparse because the only nonzero elements are on (or near) the diagonal, and 
other elements are exactly zero. The sparsity of a matrix is reported as the proportion of elements that are zero.
For an <em>n</em>&nbsp;x&nbsp;<em>m</em>, this is  card(A[i,j] = 0) / (<em>n*m</em>), which is a number in [0,1].
Very sparse matrices have sparsity close to 1, whereas very dense matrices have sparsity close to 0.
An <em>n</em>&nbsp;x&nbsp;<em>n</em> diagonal matrix has sparsity 1 &ndash; 1/<em>n</em>.
</p>

<p>
Hoyer's measure of sparseness is for vectors, not matrices. 
It does not count zeros and non-zeros. Rather, it looks at the magnitudes of the elements and measures how many are relatively large and how many are 
relatively small. If all elements are approximately the same magnitude, the vector has a sparseness measure that is close to 0. If there are a small number of large elements and the other elements are small, the vector has a measure that is close to 1.
</p>


<h3>Hoyer's sparseness measure</h3>
<p>
Hoyer's sparseness measure is defined as follows. If <strong>v</strong> &ne; <strong>0</strong> is an <em>n</em>-dimensional vector, <em>n</em> &ge; 2, the 
sparseness of <strong>v</strong> is defined as
<br /> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<span class='MathJax_Preview'>\(S(v) = \frac{\sqrt{n} - \|v\|_1 / \|v\|_2}{\sqrt{n} - 1}\)</span><script type='math/tex'>S(v) = \frac{\sqrt{n} - \|v\|_1 / \|v\|_2}{\sqrt{n} - 1}</script>
<br /> 
where
<span class='MathJax_Preview'>\(\|v\|_1 = \sum_{i=1}^n |v_i|\)</span><script type='math/tex'>\|v\|_1 = \sum_{i=1}^n |v_i|</script> is the L<sub>1</sub> vector norm, and 
<span class='MathJax_Preview'>\(\|v\|_2 = \sqrt{ \sum_{i=1}^n v_i^2 }\)</span><script type='math/tex'>\|v\|_2 = \sqrt{ \sum_{i=1}^n v_i^2 }</script> is the L<sub>2</sub> or Euclidean norm.
</p>
<p>
Clearly, the ratio of the L<sub>1</sub> norm to the L<sub>2</sub> norm is an important quantity. There are a few easy mathematical results about the ratio <span class='MathJax_Preview'>\(\|v\|_1 / \|v\|_2\)</span><script type='math/tex'>\|v\|_1 / \|v\|_2</script>:
</p>
<ol>
<li>The ratio is invariant under a change of signs for any component. That is, if S is a diagonal matrix whose diagonal elements are &plusmn;1, 
then <span class='MathJax_Preview'>\(\|S v\| = \|v\|\)</span><script type='math/tex'>\|S v\| = \|v\|</script>.
</li>
<li>The ratio is scale invariant. For any <em>c</em> &ne; 0, scalar multiplication by <em>c</em> does not change the ratio: <span class='MathJax_Preview'>\(\|c v\|_1 / \|c v\|_2 =  \|v\|_1 / \|v\|_2\)</span><script type='math/tex'>\|c v\|_1 / \|c v\|_2 =  \|v\|_1 / \|v\|_2</script>.
</li>
<li>The ratio is permutation invariant because the norms are permutation invariant. That is, if you permute the coordinates of <strong>v</strong>, you do not change the norm or the ratio of norms. In terms of matrices, if P is any permutation matrix,  <span class='MathJax_Preview'>\(\|P v\| = \|v\|\)</span><script type='math/tex'>\|P v\| = \|v\|</script>.
</li>
</ol>

<p>
The first property tell you that it suffices to understand the ratio of norms for "positive vectors" in the first octant (that is, all components are positive). The second property tells you that you can standardize the vectors to have unit L<sub>2</sub> norm.
The third property tells you that you can assume that |v<sub>1</sub>| &ge; |v<sub>2</sub>| &ge; &hellip; &ge; |v<sub>2</sub>|, if you want to.
These properties are apparent in the subsequent visualizations.
</p>


<h3>The range of the Hoyer measure</h3>
<p>
For any nonzero <em>n</em>-dimensional vector, the Hoyer measure of sparseness is in the interval [0, 1].
</p>
<p>
The Hoyer measure achieves its maximum value of 1 when (<em>n</em>-1) components of <strong>v</strong> are zero and the remaining component is nonzero. 
The properties in the previous section enable us to assume, without loss of generality, that the first component is nonzero. Then,
||v||<sub>1</sub> = ||v||<sub>2</sub>, so the ratio of norms is 1. It follows that S(<strong>v</strong>) = 1 when <strong>v</strong> has only one non-zero component.
</p>
<p>
The Hoyer measure achieves its minimum value of 0 when all components of <strong>v</strong> are equal. 
The properties in the previous section enable us to standardize the vector so that its Euclidean norm is 1.
If all components are equal, then ||<strong>v</strong>||<sub>2</sub> = sqrt(&Sigma; <em>a</em><sup>2</sup>) = 1 for some value, <em>a</em>.
Thus, <em>a</em> = &plusmn;1/sqrt(<em>n</em>). Consequently, ||<strong>v</strong>||<sub>1</sub> = <em>n</em> / sqrt(<em>n</em>) = sqrt(<em>n</em>), so the numerator of Hoyer's measure vanishes. It follows that S(<strong>v</strong>) = 0 when all components of <strong>v</strong> are equal.
</p>

<h3>Examples of Hoyer's sparseness</h3>
<a href="https://blogs.sas.com/content/iml/files/2026/07/sparseness5.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/sparseness5.png" alt="" width="300" height="400" class="alignright size-full wp-image-59538" srcset="https://blogs.sas.com/content/iml/files/2026/07/sparseness5.png 300w, https://blogs.sas.com/content/iml/files/2026/07/sparseness5-225x300.png 225w" sizes="(max-width: 300px) 100vw, 300px" /></a>

<p>
Let's illustrate this concept with an example. Consider the following 6-D vectors. To within four decimal places, the Euclidean (or L2) norm of each vector is 1.
</p>
<ul>
<li><strong>q</strong> = {0.4082, 0.4082, 0.4082, 0.4082, 0.4082, 0.4082}. The L1 norm is 2.4492, and the Hoyer sparseness is 0.
</li>
<li><strong>s</strong> = {0.5774, 0.5774, 0.5774, 0, 0, 0}. The L1 norm is 1.7322, and the Hoyer sparseness is 0.4950.
</li>
<li><strong>u</strong> = {0.9948, 0.08, 0.04, 0.04, 0.02, 0.02}. The L1 norm is 1.1948, and the Hoyer sparseness is 0.8656. Notice that the sparseness is high even though no element is exactly 0.
</li>
</ul>

<p>
The panel of graphs to the right visualizes the meaning of Hoyer's sparseness measure. Vectors that have a few elements that are larger than the other have the most sparseness. Vectors whose components are approximately uniformly distributed have the least sparseness.
</p>


<h3>Visualize Hoyer's sparseness for 2-D vectors</h3>
<p>
Since Hoyer's measure is invariant under scaling, it suffices to visualize the measure for unit vectors.
If the vectors are two-dimensional, the unit vectors are on the unit circle centered at the origin.
From the previous section, we know that the measure will obtain its maximum value of 1 for vectors that are aligned with a coordinate axis.
It will obtain its minimum value of 0 when the components are equal in magnitude.
In the first quadrant, this occurs when the vector makes an angle of 45 degrees or &pi;/4 radians. By the permutation invariance, there
are also minima for vectors at 3&pi;/4, 5&pi;/4, and 7&pi;/4 radians. 
</p>
<p>
The following SAS DATA step visualizes the Hoyer sparseness measure for 2-D vectors on the unit circle:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* A measure of sparsity of a vector from:
   Patrick O. Hoyer. Non-negative matrix factorization with sparseness constraints. 
                     Journal of Machine Learning Research, 5:1457–1469, 2004.
   The &quot;most sparse&quot; vector is one for which only a single component is non-zero.
   That is, it aligns with a coordinate axis. These vectors have a sparseness measure of 1.
   The &quot;least sparse&quot; vector is one for which elements are equal. These vectors have a sparseness of 0.
*/</span>
<span style="color: #006400; font-style: italic;">/* first, visualize for v on the unit circle in 2-D */</span>
<span style="color: #000080; font-weight: bold;">data</span> circle_Hoyer;
   sn = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   pi = constant<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'pi'</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">do</span> theta = <span style="color: #2e8b57; font-weight: bold;">0</span> to <span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #006400; font-style: italic;">*pi by pi/100;</span>
      <span style="color: #0000ff;">x</span> = <span style="color: #0000ff;">cos</span><span style="color: #66cc66;">&#40;</span>theta<span style="color: #66cc66;">&#41;</span>;
      y = <span style="color: #0000ff;">sin</span><span style="color: #66cc66;">&#40;</span>theta<span style="color: #66cc66;">&#41;</span>;
      L1 = lpnorm<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #0000ff;">x</span>, y<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* L1 norm of v=(x,y) */</span>
      Sparseness = <span style="color: #66cc66;">&#40;</span>sn - L1<span style="color: #66cc66;">&#41;</span> / <span style="color: #66cc66;">&#40;</span>sn-<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">output</span>;
   <span style="color: #0000ff;">end</span>;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* use Spectral color ramp from ColorBrewer */</span>
<span style="color: #0000ff;">%let</span> spectral = CX3288BD CX99D594 CXE6F598 CXFFFFBF CXFEE08B CXFC8D59 CXD53E4F;
<span style="color: #006400; font-style: italic;">/* Visualize Hoyer's sparseness measure on the unit circle */</span>
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Sparseness on the Unit Circle&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=circle_Hoyer aspect=<span style="color: #2e8b57; font-weight: bold;">1</span>;
   scatter <span style="color: #0000ff;">x</span>=<span style="color: #0000ff;">x</span> y=y / colorresponse=Sparseness colormodel=<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff; font-weight: bold;">&amp;spectral</span><span style="color: #66cc66;">&#41;</span> markerattrs=<span style="color: #66cc66;">&#40;</span>symbol=CircleFilled<span style="color: #66cc66;">&#41;</span>;
   gradlegend / <span style="color: #0000ff;">title</span>=<span style="color: #a020f0;">&quot;L1 Norm&quot;</span>;
   xaxis grid; yaxis grid;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/07/sparseness1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/sparseness1.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59550" srcset="https://blogs.sas.com/content/iml/files/2026/07/sparseness1.png 640w, https://blogs.sas.com/content/iml/files/2026/07/sparseness1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
As expected, the visualization shows that the Hoyer sparseness measure is 0 (blue) for the vectors (&plusmn;1/&radic;2, &plusmn;1/&radic;2), which have components that are equal in magnitude.
The measure is 1 (red) for vectors on a coordinate axis.
Notice that I used <a href="https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lefunctionsref/n0ppk0d4ci0ai7n1c1tlkqc1j4ff.htm">the LPNORM function in Base SAS</a> to compute the L1 norm of the vectors.
</p>


<h3>Visualize Hoyer's sparseness for 3-D vectors</h3>
<p>
In a similar way, you can visualize the sparseness of 3-D unit vectors. 
The measure will obtain its maximum value of 1 for vectors that are aligned with a coordinate axis.
It will obtain its minimum value of 0 when the vectors components are equal in magnitude: (&plusmn;1/&radic;3, &plusmn;1/&radic;3, &plusmn;1/&radic;3).
</p>
<p>
The following SAS DATA step visualizes the Hoyer sparseness measure for 3-D vectors on the upper hemisphere of the unit sphere:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Generate the grid for the upper hemisphere */</span>
<span style="color: #000080; font-weight: bold;">data</span> sphere_L1;
   sn = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#41;</span>;
   delta = <span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #006400; font-style: italic;">**(-5);</span>     <span style="color: #006400; font-style: italic;">/* Set the resolution of the grid */</span>
   <span style="color: #006400; font-style: italic;">/* Loop over (x,y) in unit circle, subset of [-1,1]x[-1,1] */</span>
   <span style="color: #0000ff;">do</span> <span style="color: #0000ff;">x</span> = -<span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">by</span> delta;
      <span style="color: #0000ff;">do</span> y = -<span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">by</span> delta;
         <span style="color: #0000ff;">if</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #006400; font-style: italic;">**2 + y**2) &lt;= 1 then do;</span> <span style="color: #006400; font-style: italic;">/* restrict to unit circle */</span>
            z = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span> - <span style="color: #0000ff;">x</span><span style="color: #006400; font-style: italic;">**2 - y**2);</span>  <span style="color: #006400; font-style: italic;">/* upper hemisphere */</span>
            L1 = lpnorm<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #0000ff;">x</span>, y, z<span style="color: #66cc66;">&#41;</span>;    <span style="color: #006400; font-style: italic;">/* L1 norm of v=(x,y,z) */</span>
            Sparseness = <span style="color: #66cc66;">&#40;</span>sn - L1<span style="color: #66cc66;">&#41;</span> / <span style="color: #66cc66;">&#40;</span>sn-<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
            <span style="color: #0000ff;">output</span>;
         <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">drop</span> delta;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* use Spectral color ramp from ColorBrewer */</span>
<span style="color: #0000ff;">%let</span> spectral = CX3288BD CX99D594 CXE6F598 CXFFFFBF CXFEE08B CXFC8D59 CXD53E4F;
<span style="color: #006400; font-style: italic;">/* Visualize the L1 norm as a heatmap */</span>
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Heatmap of Sparseness on the Upper Hemisphere of the Unit Sphere&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=sphere_L1 aspect=<span style="color: #2e8b57; font-weight: bold;">1</span>;
   heatmapparm <span style="color: #0000ff;">x</span>=<span style="color: #0000ff;">x</span> y=y colorresponse=Sparseness / colormodel=<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff; font-weight: bold;">&amp;spectral</span><span style="color: #66cc66;">&#41;</span>;        
   gradlegend / <span style="color: #0000ff;">title</span>=<span style="color: #a020f0;">&quot;L1 Norm&quot;</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/07/sparseness2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/sparseness2.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59547" srcset="https://blogs.sas.com/content/iml/files/2026/07/sparseness2.png 640w, https://blogs.sas.com/content/iml/files/2026/07/sparseness2-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
Because of the symmetries in the Hoyer sparseness measure, it suffices to visualize the measure on vectors in first octant where all elements are nonnegative. In the following image, I used a DROPLINE statement to locate the vector that has minimal Hoyer sparseness, which is (1/sqrt(3), 1/sqrt(3), 1/sqrt(3)).
</p>

<a href="https://blogs.sas.com/content/iml/files/2026/07/sparseness3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/sparseness3.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59544" srcset="https://blogs.sas.com/content/iml/files/2026/07/sparseness3.png 640w, https://blogs.sas.com/content/iml/files/2026/07/sparseness3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>



<h3>A SAS function to compute Hoyer's sparseness</h3>
<p>
In SAS, the IML language is the natural place to implement a vector measure because the language supports vector operations in a natural way.
The following PROC IML statements define a function that computes Hoyer's sparseness for a vector:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* Hoyer's sparseness measure.
   For a column vector, v, this function returns 
   the Hoyer (2004) measure of sparseness
   s = (sqrt(n) - ||v||_1 / ||v||_2) / (sqrt(n)-1)
*/</span>
start Sparseness<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
   u = colvec<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;    <span style="color: #006400; font-style: italic;">/* always treat input as a column vector */</span>
   L2 = norm<span style="color: #66cc66;">&#40;</span>u, <span style="color: #a020f0;">&quot;L2&quot;</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">if</span> L2=<span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span>.<span style="color: #66cc66;">&#41;</span>;
   L1 = norm<span style="color: #66cc66;">&#40;</span>u, <span style="color: #a020f0;">&quot;L1&quot;</span><span style="color: #66cc66;">&#41;</span>;
   sn = <span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span> countn<span style="color: #66cc66;">&#40;</span>u<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
   sparseness = <span style="color: #66cc66;">&#40;</span>sn - L1/L2<span style="color: #66cc66;">&#41;</span> / <span style="color: #66cc66;">&#40;</span>sn - <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> clip01<span style="color: #66cc66;">&#40;</span>sparseness<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
<span style="color: #006400; font-style: italic;">/* Helper function: clip values into [0,1]
   If x is any matrix, return a new matrix whose i_th element has the value 
   0    if x[i] &lt; 0
   x[i] if 0 &lt;= x[i] &lt;= 1
   1    if x[i] &gt; 1
   See https://blogs.sas.com/content/iml/2026/02/04/clip-values.html */</span>
start clip01<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> <span style="color: #2e8b57; font-weight: bold;">0</span> &lt;&gt; <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span> &gt;&lt; <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Examples: Compute the Hoyer measure for these columns */</span>
<span style="color: #006400; font-style: italic;">/*   q       r  s       t    u       v */</span>  
A = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0.5774</span>  <span style="color: #2e8b57; font-weight: bold;">3.3</span>  <span style="color: #2e8b57; font-weight: bold;">0.9948</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>,
     <span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0.5774</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>    <span style="color: #2e8b57; font-weight: bold;">0.08</span>    <span style="color: #2e8b57; font-weight: bold;">0</span>,
     <span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0.5774</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>    <span style="color: #2e8b57; font-weight: bold;">0.04</span>    <span style="color: #2e8b57; font-weight: bold;">0</span>,
     <span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0</span>       <span style="color: #2e8b57; font-weight: bold;">0.3</span>  <span style="color: #2e8b57; font-weight: bold;">0.04</span>    <span style="color: #2e8b57; font-weight: bold;">0</span>,
     <span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0</span>       <span style="color: #2e8b57; font-weight: bold;">0.2</span>  <span style="color: #2e8b57; font-weight: bold;">0.02</span>    <span style="color: #2e8b57; font-weight: bold;">0</span>,
     <span style="color: #2e8b57; font-weight: bold;">0.4082</span>  <span style="color: #2e8b57; font-weight: bold;">1</span>  <span style="color: #2e8b57; font-weight: bold;">0</span>       <span style="color: #2e8b57; font-weight: bold;">0.2</span>  <span style="color: #2e8b57; font-weight: bold;">0.02</span>    <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#125;</span>;
&nbsp;
S = j<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, ncol<span style="color: #66cc66;">&#40;</span>A<span style="color: #66cc66;">&#41;</span>, .<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">do</span> i = <span style="color: #2e8b57; font-weight: bold;">1</span> to ncol<span style="color: #66cc66;">&#40;</span>A<span style="color: #66cc66;">&#41;</span>;
   S<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = Sparseness<span style="color: #66cc66;">&#40;</span> A<span style="color: #66cc66;">&#91;</span>,i<span style="color: #66cc66;">&#93;</span> <span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">end</span>;
print S<span style="color: #66cc66;">&#91;</span>F=<span style="color: #2e8b57; font-weight: bold;">6.4</span> c=<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'q'</span>:<span style="color: #a020f0;">'w'</span><span style="color: #66cc66;">&#41;</span> L=<span style="color: #a020f0;">&quot;Hoyer's Sparseness&quot;</span><span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/sparseness4.png" alt="" width="313" height="98" class="alignnone size-full wp-image-59586" srcset="https://blogs.sas.com/content/iml/files/2026/07/sparseness4.png 313w, https://blogs.sas.com/content/iml/files/2026/07/sparseness4-300x94.png 300w" sizes="(max-width: 313px) 100vw, 313px" />

<p>
The table shows the sparseness for each column of the A matrix. 
</p>
<ul>
<li>
The first and second columns (q and r) both have sparseness 0 because both columns are constant in value. The first column has unit L2 norm whereas the second column does not. But it doesn't matter, because the Hoyer measure is normalized by using the ratio 
||<strong>v</strong>||<sub>1</sub> / ||<strong>v</strong>||<sub>2</sub>.
</li>
<li>
The third column (s) has three zero elements and three equal and nonzero elements.  The sparseness measure is 0.495.
</li>
<li>
The fourth column (t) has one large element, two medium-sized elements, and three small but nonzero elements.
The sparseness measure is 0.5445.
</li>
<li>
The fifth column (u) has one large element and five small but nonzero elements.
The sparseness measure is 0.8656.
</li>
<li>
The last column (v) has one large element and five elements that are zero.
The sparseness measure is 1, which is the largest possible value of sparseness.
</li>
</ul>

<p>
An earlier section visualized three of these columns in a panel of needle plots.
</p>

<h3>Summary</h3>
<p>
This article introduces the Hoyer sparseness measure, S(<strong>v</strong>), for nonzero vectors <strong>v</strong>.
The measure indicates whether a vector has many components that are the same magnitude (low sparseness) as opposed to vectors that have many small components and only a few components that are large in magnitude. The sparseness measure is invariant to scaling, permutation of components, and switching the signs of components. Accordingly, you can visualize the measure by using vectors whose components are all positive and whose Euclidean length is 1.
</p>
<p>
A future article shows <a href="https://blogs.sas.com/content/iml/2026/08/03/nmf-sparseness.html">an application of the Hoyer measure to the nonnegative matrix factorization (NMF) method</a>.
</p>



<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html">Hoyer&#039;s sparseness measure</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/07/27/hoyer-sparseness.html/feed</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/07/sparseness2-150x150.png" />
	</item>
		<item>
		<title>The log-KDE method: Density estimates for positive data</title>
		<link>https://blogs.sas.com/content/iml/2026/07/20/log-kde-positive-data.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/07/20/log-kde-positive-data.html#comments</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 20 Jul 2026 09:20:46 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Data Analysis]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=59447</guid>

					<description><![CDATA[<p>A previous article discusses the problem of fitting a kernel density estimate (KDE) to data that are strictly positive. If you use a standard KDE, the resulting density curve might estimate non-zero probability for negative values. This is unsatisfactory because quantities like lengths and mass cannot be negative. The previous [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/20/log-kde-positive-data.html">The log-KDE method: Density estimates for positive data</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
A previous article discusses <a href="https://blogs.sas.com/content/iml/2026/07/13/kde-positive.html">the problem of fitting a kernel density estimate (KDE) to data that are strictly positive</a>. 
If you use a standard KDE, the resulting density curve might estimate non-zero probability for negative values. 
This is unsatisfactory because quantities like lengths and mass cannot be negative.
The previous article shows how to use PROC KDE in SAS to truncate the KDE (and optionally renormalize the density).
</p>
<p>
Unfortunately, truncation has a known statistical problem: the density is biased near the boundary x=0 (Silverman, 1986).
This article demonstrates an alternative: the log-KDE method. 
The log-KDE method guarantees that the estimated density is strictly positive and integrates to unity.
Furthermore, the resulting density estimate is not biased near the boundary.
</p>

<h3>The Log-KDE method</h3>
<p>
Let X be a strictly positive random variable. We want to compute a nonparametric density estimate of the distribution of X.
The log-KDE method consists of three steps:
</p>
<ol>
<li>Log-transform the strictly positive data: <em>Y</em> = log(<em>X</em>).</li>
<li>Fit a standard kernel density estimate to the transformed data, <em>Y</em>.</li>
<li>Back-transform the density to the original scale.</li>
</ol>

<p>
Step 3 is a little tricky.
Let <em>f<sub>Y</sub></em>(<em>y</em>) be the KDE on the log scale.
When you back-transform the density to the data scale, 
you must apply a change-of-variables formula, also known as the chain rule or the Jacobian of the transformation.
This is familiar to students of integral calculus: when you change variables inside an integral, you also must 
change the differential.
</p><p>
For a general change-of-variables formula, consider the integral
<br />
&int;  <em>f<sub>Y</sub>(y) dy</em>
<br />
If you represent this integral in terms of x by making the substitution <em>y</em> = log(<em>x</em>),
then <em>dy</em> =  <em>dy/dx</em> <em>dx</em> = (1/<em>x</em>)  <em>dx</em>.
Thought of as measures, 
the back-transformed density is therefore
<br />
<em>f<sub>X</sub></em>(<em>x</em>) = <em>f<sub>Y</sub></em>(log(<em>x</em>)) (1/<em>x</em>)
</p>

<h3>Implement the method in SAS</h3>
<p>
Let's implement the log-KDE method for the same `Bulkhead` dataset used in the previous article. 
The data represent the position of a rivet relative to a bulkhead, which must be a positive quantity.
The following DATA step defines the data.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">data</span> Bulkhead;
<span style="color: #0000ff;">input</span> Position @@;
datalines;
 <span style="color: #2e8b57; font-weight: bold;">2.44</span>  <span style="color: #2e8b57; font-weight: bold;">6.84</span> <span style="color: #2e8b57; font-weight: bold;">16.84</span>  <span style="color: #2e8b57; font-weight: bold;">3.76</span>  <span style="color: #2e8b57; font-weight: bold;">5.58</span> <span style="color: #2e8b57; font-weight: bold;">14.53</span> <span style="color: #2e8b57; font-weight: bold;">12.12</span>  <span style="color: #2e8b57; font-weight: bold;">8.00</span>  <span style="color: #2e8b57; font-weight: bold;">8.66</span>  <span style="color: #2e8b57; font-weight: bold;">6.63</span>  <span style="color: #2e8b57; font-weight: bold;">9.51</span>  <span style="color: #2e8b57; font-weight: bold;">6.37</span> <span style="color: #2e8b57; font-weight: bold;">11.78</span>
 <span style="color: #2e8b57; font-weight: bold;">6.14</span>  <span style="color: #2e8b57; font-weight: bold;">9.04</span>  <span style="color: #2e8b57; font-weight: bold;">2.88</span> <span style="color: #2e8b57; font-weight: bold;">21.28</span>  <span style="color: #2e8b57; font-weight: bold;">8.43</span> <span style="color: #2e8b57; font-weight: bold;">18.91</span>  <span style="color: #2e8b57; font-weight: bold;">4.45</span>  <span style="color: #2e8b57; font-weight: bold;">4.59</span> <span style="color: #2e8b57; font-weight: bold;">20.42</span>  <span style="color: #2e8b57; font-weight: bold;">9.03</span>  <span style="color: #2e8b57; font-weight: bold;">6.55</span>  <span style="color: #2e8b57; font-weight: bold;">7.15</span>  <span style="color: #2e8b57; font-weight: bold;">4.10</span>
 <span style="color: #2e8b57; font-weight: bold;">8.72</span>  <span style="color: #2e8b57; font-weight: bold;">5.17</span>  <span style="color: #2e8b57; font-weight: bold;">2.41</span> <span style="color: #2e8b57; font-weight: bold;">10.93</span> <span style="color: #2e8b57; font-weight: bold;">14.08</span>  <span style="color: #2e8b57; font-weight: bold;">5.44</span>  <span style="color: #2e8b57; font-weight: bold;">2.81</span>  <span style="color: #2e8b57; font-weight: bold;">3.99</span>  <span style="color: #2e8b57; font-weight: bold;">8.40</span> <span style="color: #2e8b57; font-weight: bold;">11.28</span>  <span style="color: #2e8b57; font-weight: bold;">3.97</span> <span style="color: #2e8b57; font-weight: bold;">18.99</span> <span style="color: #2e8b57; font-weight: bold;">10.51</span>
 <span style="color: #2e8b57; font-weight: bold;">9.52</span>  <span style="color: #2e8b57; font-weight: bold;">2.01</span>  <span style="color: #2e8b57; font-weight: bold;">1.5</span> 
;
<span style="color: #0000ff;">%let</span> <span style="color: #0000ff;">DSName</span>  = Bulkhead;
<span style="color: #0000ff;">%let</span> <span style="color: #0000ff;">varName</span> = Position;</pre></td></tr></table></div>




<p>
First, use the DATA step to log-transform the data,
Then, use PROC KDE to estimate the density on the unbounded log scale. 
You can use the OUT= option to save the evaluation points and density values to a dataset, as follows:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* 1. Log-transform the data */</span>
<span style="color: #000080; font-weight: bold;">data</span> LogData;
  <span style="color: #0000ff;">set</span> &amp;<span style="color: #0000ff;">DSName</span>;
  Log_&amp;<span style="color: #0000ff;">varName</span> = <span style="color: #0000ff;">log</span><span style="color: #66cc66;">&#40;</span>&amp;<span style="color: #0000ff;">varName</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* 2. Fit a density estimate to the transformed data */</span>
<span style="color: #000080; font-weight: bold;">proc kde</span> <span style="color: #000080; font-weight: bold;">data</span>=LogData;
   univar Log_&amp;<span style="color: #0000ff;">varName</span> / unistats 
          out=KDEOUT_Log<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">rename</span>=<span style="color: #66cc66;">&#40;</span>value=log_value density=KDE_LogDensity<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<p>
The 'KDEOUT_Log' data set contains the density (KDE_LogDensity) of the log-transformed variable (log_value).
Next, use the DATA step to apply the change-of-variables formula, which maps the log-density back to the data scale:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* 3. Back-transform the density using f_X(x) = f_Y(log(x)) * (1/x), where 1/x is the Jacobian */</span>
<span style="color: #000080; font-weight: bold;">data</span> KDEOUT;
   <span style="color: #0000ff;">set</span> KDEOUT_Log;
   <span style="color: #0000ff;">x</span> = <span style="color: #0000ff;">exp</span><span style="color: #66cc66;">&#40;</span>log_value<span style="color: #66cc66;">&#41;</span>;
   KDE_Density = KDE_LogDensity / <span style="color: #0000ff;">x</span>;  
   <span style="color: #0000ff;">drop</span> <span style="color: #0000ff;">var</span> log_value KDE_LogDensity;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<h3>Visualize the back-transformed KDE</h3>
<p>
You can overlay the newly calculated density curve (in the KDE_Density variable), onto a histogram of the original data. 
As shown in a previous article, you can <a href="https://blogs.sas.com/content/iml/2025/10/20/overlay-curves-histogram.html">overlay custom density curves on a histogram</a> by using the <a href="https://blogs.sas.com/content/iml/2025/10/13/high-low-emulate-histogram.html">%EmulateHistogram macro</a>.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Create macro variables for histogram bins and observation count.
   Download the macro from https://blogs.sas.com/content/iml/2025/10/13/high-low-emulate-histogram.html */</span>
%EmulateHistogram<span style="color: #66cc66;">&#40;</span>dsIn=&amp;<span style="color: #0000ff;">DSName</span>, varIn=&amp;<span style="color: #0000ff;">varName</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Scale the density to the percentage (or count) scale of the histogram */</span>
<span style="color: #000080; font-weight: bold;">data</span> All;
   <span style="color: #0000ff;">set</span> &amp;<span style="color: #0000ff;">DSName</span> _HistBins KDEOut;
   KDE_Percent = <span style="color: #2e8b57; font-weight: bold;">100</span>    <span style="color: #006400; font-style: italic;">* &amp;_binWidth * KDE_Density;</span>
   KDE_Count   = <span style="color: #0000ff; font-weight: bold;">&amp;_NOBS</span> <span style="color: #006400; font-style: italic;">* &amp;_binWidth * KDE_Density;</span>
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* 4) Overlay on a histogram */</span>
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Back-Transform of Fit of log(Position)&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=All noautolegend;
   highlow <span style="color: #0000ff;">x</span>=_midpt_ low=_zero_ high=_pct_ / type=bar barwidth=<span style="color: #2e8b57; font-weight: bold;">1</span>;
   series <span style="color: #0000ff;">x</span>=<span style="color: #0000ff;">x</span> y=KDE_Percent;
   fringe &amp;<span style="color: #0000ff;">varName</span>;
   yaxis <span style="color: #0000ff;">min</span>=<span style="color: #2e8b57; font-weight: bold;">0</span> offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span> grid;
   xaxis values=<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">0</span> to <span style="color: #0000ff; font-weight: bold;">&amp;_binEnd</span> <span style="color: #0000ff;">by</span> <span style="color: #0000ff; font-weight: bold;">&amp;_binWidth</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<figure id="attachment_59456" aria-describedby="caption-attachment-59456" style="width: 480px" class="wp-caption alignnone"><a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc6.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc6.png" alt="" width="480" height="360" class="size-full wp-image-59456" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc6.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc6-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a><figcaption id="caption-attachment-59456" class="wp-caption-text">Log-KDE for Positive Data</figcaption></figure>

<p>
Notice that the KDE is zero when the Position variable is negative. Furthermore, this KDE integrates to unity.
The estimate of the KDE near zero is data dependent.
For these data, the KDE drops smoothly to zero at Positon=0. 
Contrast that with the estimate in the previous article, which predicted a nonzero probability when Position &asymp; 0.
</p>

<h3>Peculiarities of the log-KDE method</h3>
<p>
It is important to understand that this log-KDE estimate is different from the standard KDE on the data scale.
Most KDEs use a constant bandwidth for the kernels. 
Intuitively, <a href="https://blogs.sas.com/content/iml/2016/07/27/visualize-kernel-density-estimate.html">you can visualize the KDE as the sum of a bunch of little bump functions</a> (the kernels) centered at each data point. The most common bump functions a Gaussian in shape.
But that is not true for the log-KDE method.
</p><p>
For the log-KDE method, Gaussian kernels are used to fit the density on the log scale.
But when you back-transform the density, the Gaussian kernels are transformed into lognormal kernels. (Recall that the definition of a lognormal variable is one whose logarithm is normal.)
Furthermore, the bandwidth of the lognormal kernels is not uniform. 
</p><p>
To see this, consider a Gaussian kernel centered on the point <em>y</em><sub>i</sub> = log(<em>x</em><sub>i</sub>) in the log-scale coordinate system.
It has a bandwidth, <em>h</em>, which determines the scale of the kernel.
The back-transformation maps the Gaussian kernel to a lognormal kernel that has
location parameter log(<em>x</em><sub>i</sub>) and scale parameter <em>h</em>.
<a href="https://en.wikipedia.org/wiki/Log-normal_distribution">The standard deviation of that lognormal distribution</a> is therefore
<br />
exp(log(<em>x</em><sub>i</sub>) + h<sup>2</sup>/2) sqrt(exp(h<sup>2</sup>) - 1)
<br />
or 
<br />
<em>x</em><sub>i</sub> exp(h<sup>2</sup>/2) sqrt(exp(h<sup>2</sup>) - 1)
<br />
</p><p>
The interpretation of this formula is that the width of the (lognormal) kernels on the data scale are 
proportional to the data value, 
<em>x</em><sub>i</sub>.
For data values much less than 1, the kernels on the data scale are tall and narrow
whereas for values much greater than 1, the kernels on the data scale are short and broad.
</p><p>
For example, suppose you have the data values {0.2, 0.5, 1, 2}.
These get log-transformed and the KDE is computed on the log scale by placing a Gaussian density at 
log(<em>x</em><sub>i</sub>) for each <em>i</em>.
Now consider what these bell-shaped curves will look like when they are back-transformed.
Each becomes a lognormal curve, but the width of those curves is not constant.
This is illustrated in the following graph, which shows 
the kernels on the data scale for the data values {0.2, 0.5, 1, 2}.
</p>


<a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc7.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc7.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59468" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc7.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc7-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
As you can see, the kernel based at <em>x</em>=0.2 is tall and narrow, whereas the kernel based at <em>x</em>=2 is broad and flat.
</p>
<p>
Because of this, you might notice spikes in the log-KDE for 
data that are near 0. For example, if you were to add a new  data point to the Bulkhead data set for Position=0.5, 
the log-KDE will become bimodal and exhibit a new wiggle near that location.
</p>

<h3>Summary</h3>
<p>
This article shows how to use the log-KDE method to estimate the density of strictly positive data.
The method is guaranteed to estimate a density who support is strictly positive.
Furthermore, the resulting density estimate is not biased near the boundary.
However, geometrically, this is achieved by using lognormal kernels with nonconstant 
bandwidths. The standard deviation of a lognormal kernel is proportional to the data value at which the kernel is based.
</p>

<h3>Further Reading</h3>
<p>
Jones, A. T., Nguyen, H. D., &amp; McLachlan, G. J. (2018). <a href="https://cran.r-project.org/web/packages/logKDE/vignettes/logKDE.pdf">"Kernel density estimation on positive data via the logKDE package for R."</a> The
<a href="https://cran.r-project.org/web/packages/logKDE/index.html">logKDE package</a> is available on CRAN.

</p><p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/20/log-kde-positive-data.html">The log-KDE method: Density estimates for positive data</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/07/20/log-kde-positive-data.html/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc6-150x150.png" />
	</item>
	</channel>
</rss>
