<?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>Tue, 13 Oct 2026 19:26:09 +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>The modified Bessel function of the second kind in SAS</title>
		<link>https://blogs.sas.com/content/iml/2026/10/12/besselk-function-sas.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/10/12/besselk-function-sas.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 09:23:02 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Numerical Analysis]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=58936</guid>

					<description><![CDATA[<p>Bessel functions appear in surprising places. Although originally studied in physics as the solution to differential equations that describe waves and heat diffusion, Bessel functions also have applications in probability, statistics, and signal processing. SAS software provides the JBESSEL function, which is a Bessel function of the first kind, and [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/10/12/besselk-function-sas.html">The modified Bessel function of the second kind 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>
<a href="https://en.wikipedia.org/wiki/Bessel_function">Bessel functions</a> appear in surprising places. 
Although originally studied in physics as the solution to differential equations that describe waves and heat diffusion,
Bessel functions also have applications in probability, statistics, and signal processing.
SAS software provides <a href="https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lefunctionsref/p13zbsyioy421cn1ipphn1edwis1.htm">the JBESSEL function</a>, which is a Bessel function of the first kind,
and <a href="https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lefunctionsref/n123ec4afwk7ben1ilrz6uor45ut.htm">the IBESSEL function</a>, which is a modified Bessel function of the first kind. <a href="https://blogs.sas.com/content/iml/2015/05/20/nuclear-bessels.html">I have previously written about these functions.</a>
</p>
<p>
A SAS customer recently asked me about how to implement the <em>modified Bessel function of the second kind</em> in SAS.
This Bessel function, which is often denoted by  <em>K</em><sub>&nu;</sub>(x), appears in the 
formulas for the generalized inverse Gaussian (GIG) distribution. The modified Bessel function of the second kind
is also used in survival analysis, spatial statistics, financial modeling, 
and to model variance parameters in Bayesian models.
Although SAS does not have a built-in "KBESSEL" function, you can define the <em>K</em><sub>&nu;</sub>(x) function 
in SAS IML. 
</p>

<h3>The definition of the modified Bessel function of the second kind</h3>
<p>
For the purpose of this article, the modified Bessel function of the second kind,
<em>K</em><sub>&nu;</sub>(x), where the parameter &nu; is a real number and the domain of the function is <em>x</em> &gt; 0.
A good way to compute the function is to use the integral definition from <a href="https://dlmf.nist.gov/10.32#E10">Equation 10.32.10</a> of the 
<em>NIST Digital Library of Mathematical Functions</em>:
<br />
<span class='MathJax_Preview'>\(
K_\nu(x) = \frac{1}{2} \left( \frac{x}{2} \right)^\nu \int_0^\infty t^{-\nu-1} \exp\left( -t - \frac{x^2}{4t} \right) \, dt
\)</span><script type='math/tex'>
K_\nu(x) = \frac{1}{2} \left( \frac{x}{2} \right)^\nu \int_0^\infty t^{-\nu-1} \exp\left( -t - \frac{x^2}{4t} \right) \, dt
</script>
</p>
<p>
In this formula, <em>x</em> and &nu; are parameters to the integrand. The dummy variable, <em>t</em>, is integrated from 0 to infinity.
The integrand has a peak that can be computed analytically by using calculus. The peak occurs at 
<br />
<span class='MathJax_Preview'>\(
t_{peak} = \frac{\sqrt{(\nu + 1)^2 + x^2} - (\nu + 1)}{2}
\)</span><script type='math/tex'>
t_{peak} = \frac{\sqrt{(\nu + 1)^2 + x^2} - (\nu + 1)}{2}
</script>

</p><p>
In the SAS IML Language, the QUAD subroutine is used to compute integrals.
Knowing the location of the peak is helpful because <a href="https://blogs.sas.com/content/iml/2014/08/13/peak-option-in-quad.html">the QUAD subroutine in IML contains a PEAK= option</a> that you can specify to assist the numerical integration routine. (Alternatively, you can perform a change of variables <em>u = t / t</em><sub>peak</sub> so that the peak is always at <em>u</em>=1. I leave that as an exercise.)
</p>


<h3>The optional exponential scaling factor</h3>
<p>
The documentation for the 
<a href="https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lefunctionsref/n123ec4afwk7ben1ilrz6uor45ut.htm">the IBESSEL function</a>, which is the modified Bessel function of the first kind, contains an optional binary argument. By default, the call
<tt>IBESSEL(nu, x)</tt> computes the function I<sub>&nu;</sub>(x). However, the function decays rapidly to zero as x increases, which makes the numerical computation difficult when x is moderately large (like x &ge; 50). Accordingly, a numerical trick is to compute the expression exp(x)*I<sub>&nu;</sub>(x), which you can do by using the syntax <tt>IBESSEL(nu, x, 1)</tt>. 
</p><p>
A similar situation occurs for the Bessel function of the second kind, K<sub>&nu;</sub>(x), which also decays quickly to zero for moderately large x.
(<a href="https://dlmf.nist.gov/10.40#E2">Equation 10.40.2</a> in the NIST Digital Library shows that the function decays asymptotically like exp(-x)/sqrt(x).)
When x is moderately large, it becomes numerically challenging to evaluate the function by using a standard numerical quadrature routine like CALL QUAD. The integration routine can prematurely terminate because the value of the integral is less than the algorithm's absolute error tolerance. 
</p>
<p>
Accordingly, it is common in numerical software for the syntax to support an optional binary flag that causes the function to return a "scaled value," which is exp(x)*K<sub>&nu;</sub>(x). The exponential scaling factor is evaluated as part of the integrand. This keeps the peak of the integrand near 1 and permits the quadrature routine to evaluate the integral accurately.
</p>
<p>
Not only does this trick enable you to compute the scaled value exp(x)*K<sub>&nu;</sub>(x) to high precision,
but it also enables you to find the value of  log(K<sub>&nu;</sub>(x)) accurately. Taking the natural log of the scaled value gives
log(exp(x)*K<sub>&nu;</sub>(x)) = x + log(K<sub>&nu;</sub>(x)), which you can solve for 
log(K<sub>&nu;</sub>(x)). This computation is shown in the next section.
The log of the Bessel function is used in applications such as loglikelihood computations.
</p>

<h3>Implement modified Bessel function of the second kind in SAS</h3>
<p>
Because SAS already supports the JBESSEL and IBESSEL functions, I will reserve the name KBESSEL for a 
future built-in SAS implementation. Instead, I will define a SAS IML function named BesselK, which takes three parameters: 
the parameter &nu;, the variable x, and an optional binary flag. If the binary flag is 0 (the default), the function will return
K<sub>&nu;</sub>(x), otherwise the function returns the scaled value exp(x)*K<sub>&nu;</sub>(x).
</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;">/* Define the integrand function for the unscaled modified 
   Bessel function of the second kind, K_{nu}(x)
 */</span>
start BesselK_integ<span style="color: #66cc66;">&#40;</span>t<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_BessK_nu, g_BessK_x<span style="color: #66cc66;">&#41;</span>;
   nu = g_BessK_nu; <span style="color: #0000ff;">x</span> = g_BessK_x;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> t<span style="color: #006400; font-style: italic;">**(-nu - 1) * exp(   -t - (x**2)/(4*t)) );</span>
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Define the integrand function that includes the EXP term:
   exp(x)*K_{nu}(x). Use this scaled function when x is moderately large or you want log(K_{nu}(x)).
*/</span>
start BesselK_Scale_integ<span style="color: #66cc66;">&#40;</span>t<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_BessK_nu, g_BessK_x<span style="color: #66cc66;">&#41;</span>;
   nu = g_BessK_nu; <span style="color: #0000ff;">x</span> = g_BessK_x;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> t<span style="color: #006400; font-style: italic;">**(-nu - 1) * exp(x -t - (x**2)/(4*t)) );</span>
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Define the wrapper function */</span>
start BesselK<span style="color: #66cc66;">&#40;</span>nu, <span style="color: #0000ff;">x</span>, scale=<span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_BessK_nu, g_BessK_x<span style="color: #66cc66;">&#41;</span>;
   g_BessK_nu = nu;
   <span style="color: #0000ff;">if</span> scale = <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> 
      funcName = <span style="color: #a020f0;">&quot;BesselK_integ&quot;</span>;
   <span style="color: #0000ff;">else</span> 
      funcName = <span style="color: #a020f0;">&quot;BesselK_Scale_integ&quot;</span>;
   <span style="color: #0000ff;">n</span> = nrow<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   K = 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>; <span style="color: #006400; font-style: italic;">/* the result vector K_nu(x). Use missing for domain error such as x &lt;= 0 */</span>
   tau = nu+<span style="color: #2e8b57; font-weight: bold;">1</span>;
   <span style="color: #0000ff;">do</span> i = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff;">n</span>;
      <span style="color: #0000ff;">if</span> <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> &gt; <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         g_BessK_x = <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>;
         <span style="color: #006400; font-style: italic;">/* Calculate the exact peak of the integrand */</span>
         pk = <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">sqrt</span><span style="color: #66cc66;">&#40;</span>tau<span style="color: #006400; font-style: italic;">**2 + x[i]**2) - tau) / 2;</span>
         <span style="color: #0000ff;">call</span> QUAD<span style="color: #66cc66;">&#40;</span>integral_at_x, funcName, <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">0</span> .I<span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#41;</span> PEAK=pk;
         K<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = integral_at_x;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #006400; font-style: italic;">/* Multiply the integral by the leading constants */</span>
   K = <span style="color: #2e8b57; font-weight: bold;">0.5</span> <span style="color: #006400; font-style: italic;">* (x / 2)##nu # K;</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span>K<span style="color: #66cc66;">&#41;</span>;
finish;
store module=<span style="color: #66cc66;">&#40;</span>BesselK_integ BesselK_Scale_integ BesselK<span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;</pre></td></tr></table></div>




<p>
Let's test the function. First, let's visualize the function 
K<sub>&nu;</sub>(x) for &nu;=1 and <em>x</em> in (0, 3].
</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: #0000ff;">x</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</span>,<span style="color: #2e8b57; font-weight: bold;">3</span>,<span style="color: #2e8b57; font-weight: bold;">0.05</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
nu = <span style="color: #2e8b57; font-weight: bold;">1</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Modified Bessel Function of the Second Kind (nu=1)&quot;</span>;
K = BesselK<span style="color: #66cc66;">&#40;</span>nu, <span style="color: #0000ff;">x</span><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>, K<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>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/10/BesselK1.png"><img fetchpriority="high" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/10/BesselK1.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60450" srcset="https://blogs.sas.com/content/iml/files/2026/10/BesselK1.png 640w, https://blogs.sas.com/content/iml/files/2026/10/BesselK1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
The graph shows that the K<sub>&nu;</sub>(x) decays to 0 rapidly. 
Let's turn on the binary flag and visualize the function 
exp(x)*K<sub>&nu;</sub>(x) on the same domain:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Scaled Modified Bessel Function exp(x)*K(x) (nu=1)&quot;</span>;
expx_K = BesselK<span style="color: #66cc66;">&#40;</span>nu, <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;">call</span> series<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, expx_K<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>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/10/BesselK2.png"><img decoding="async" src="https://blogs.sas.com/content/iml/files/2026/10/BesselK2.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60447" srcset="https://blogs.sas.com/content/iml/files/2026/10/BesselK2.png 640w, https://blogs.sas.com/content/iml/files/2026/10/BesselK2-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>


<p>
The graph shows that the exponentially scaled function still decays, but not as rapidly. 
For example, whereas K<sub>1</sub>(30) ~ 2.5E-14, the scaled function at x=30 is much larger: 
exp(30)*K<sub>1</sub>(30) ~ 0.23.
</p>

<p>
By using the scaled version, you can compute 
log(K<sub>&nu;</sub>(x)), which is useful for various applications:
</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 good is the exp scaling? 
   1. Computations are more accurate for large values of x. 
   2. You can easily compute log(K_{nu}(x)) */</span>
<span style="color: #0000ff;">x</span> = <span style="color: #2e8b57; font-weight: bold;">50</span>;
expx_K = BesselK<span style="color: #66cc66;">&#40;</span>nu, <span style="color: #0000ff;">x</span>, <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>;
logK = <span style="color: #0000ff;">log</span><span style="color: #66cc66;">&#40;</span>expx_K<span style="color: #66cc66;">&#41;</span> - <span style="color: #0000ff;">x</span>;
print <span style="color: #0000ff;">x</span> expx_K logK;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/10/BesselK3.png"><img decoding="async" src="https://blogs.sas.com/content/iml/files/2026/10/BesselK3.png" alt="" width="176" height="64" class="alignnone size-full wp-image-60453" /></a>

<p>
If you plan to compute log(K<sub>&nu;</sub>(x)) regularly, you could encapsulate this logic into 
a self-contained module.
</p>

<h3>Summary</h3>
<p>
This article shows how to implement the modified Bessel function of the second kind, K<sub>&nu;</sub>(x), in SAS.
The method involves integrating a complicated expression over an infinite domain. 
In addition, the article shows how you can compute the exponentially scaled expression exp(x)*K<sub>&nu;</sub>(x),
which enables you to evaluate log(K<sub>&nu;</sub>(x)).
</p><p>
In a subsequent article, I will show how you can use the K<sub>&nu;</sub> Bessel function to 
evaluate the PDF and log-PDF of the generalized inverse Gaussian distribution in SAS.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/10/12/besselk-function-sas.html">The modified Bessel function of the second kind 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/10/12/besselk-function-sas.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/10/BesselK1-150x150.png" />
	</item>
		<item>
		<title>What is convolution and why is it important in statistics?</title>
		<link>https://blogs.sas.com/content/iml/2026/10/05/intro-convolution.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/10/05/intro-convolution.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 05 Oct 2026 09:27:37 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Data Analysis]]></category>
		<category><![CDATA[Numerical Analysis]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=60255</guid>

					<description><![CDATA[<p>A convolution is a mathematical operation on two discrete sequences (or continuous functions) that produces a new sequence (or function). In probability and statistics, a convolution is closely associated with the concept of a "moving window," which time series analysts often call a "filter." If you have ever smoothed a [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/10/05/intro-convolution.html">What is convolution and why is it important in statistics?</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 convolution is a mathematical operation on two discrete sequences (or continuous functions) that produces a new sequence (or function). In probability and statistics, a convolution is closely associated with the concept of a "moving window," which time series analysts often call a "filter."
If you have ever smoothed a time series, created a kernel density estimate, or calculated the distribution of the sum of two independent random variables, you have used a convolution, whether you realized it or not. 
</p>

<p>This article provides the mathematical background for a discrete convolution operation.
It discusses a SAS IML function, DFCONV, that you can use to compute a discrete convolution.
The convolution operation is illustrated by using two classical statistical applications.
The first is the sum of two independent discrete random variables. The second is the 
computation of a moving average for a time series.
</p>

<h3>The formula for a discrete convolution</h3>
<p>
The convolution operation comes in two forms: a continuous convolution that operates on functions, and a discrete convolution that operates on sequences. This article discusses only the discrete convolution operator. The discrete convolution operator is a binary operation on two sequences, u and v. It produces a new sequence, denoted u*v, by using multiplication and addition of the terms in the original sequences. 
Each element of u*v is a dot product for a subsequence of elements in u and v.
</p>
<p>
In math, the two sequences are usually indexed starting at 0. That is, u = {u<sub>0</sub>, u<sub>1</sub>, ..., u<sub>M-1</sub>} and v = {v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>N-1</sub>}. The sequence u has M elements; the sequence v has N elements. The full convolution u*v is a new sequence that has M+N-1 elements (indexed from 0 to M+N-2). The k-th element of u*v, denoted as (u*v)(k), is defined as:
<br />
<span class='MathJax_Preview'>\( (u*v)(k) = \sum_{j=\max(0, k-N+1)}^{\min(k, M-1)} u(j)v(k-j) \)</span><script type='math/tex'> (u*v)(k) = \sum_{j=\max(0, k-N+1)}^{\min(k, M-1)} u(j)v(k-j) </script>
</p>

<p>
If you adopt the convention that the sequences are infinite and that the unspecified terms of the sequences are zero, 
this formula can be written more simply:
<br />
<span class='MathJax_Preview'>\( (u*v)(k) = \sum_{j=-\infty}^{\infty} u(j)v(k-j) \)</span><script type='math/tex'> (u*v)(k) = \sum_{j=-\infty}^{\infty} u(j)v(k-j) </script>
<br />
This version makes it look more like the continuous convolution operator, which replaces the summation with an integral.
The convolution is commutative, so you can also use u(k-j)v(j) in the formula.  I use this fact in the section about time-series smoothing. 
</p>


<h3>Computing a convolution: The DFCONV function in SAS IML</h3>
<p>
In Appendix A, I show how to "manually" compute a discrete convolution between two vectors in the 
SAS IML language. (The SAS IML language uses one-based indexing, so the indices in the implementation are slightly different than in the formula that I showed in the previous section.)
But, you don't need to write any code to use a convolution. The SAS IML language provides a built-in function, 
<a href="https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/imlug/imlug_langref_sect112.htm">the DFCONV function</a>, 
which automatically computes the convolution and a few useful variations. The documentation shows the formula for vectors that use one-based indexing.
The 'DF'
prefix stands for 'Digital Filter', which is the name that electrical engineers use 
for operations that process digital signals.
</p>
<p>
Let's look at a simple numeric example. Suppose that 
u = {1,2,3} and v = {4,5,6,7,8} are vectors of length M=3 and N=5, respectively.
You can use the DFCONV function to compute the convolution y = u*v, 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>;
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: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#125;</span>;
v = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">4</span>,<span style="color: #2e8b57; font-weight: bold;">5</span>,<span style="color: #2e8b57; font-weight: bold;">6</span>,<span style="color: #2e8b57; font-weight: bold;">7</span>,<span style="color: #2e8b57; font-weight: bold;">8</span><span style="color: #66cc66;">&#125;</span>;
y = dfconv<span style="color: #66cc66;">&#40;</span>u,v<span style="color: #66cc66;">&#41;</span>;
print y;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/dfconv1.png" alt="" width="37" height="231" class="alignnone size-full wp-image-60387" />

<p>Appendix A provides the step-by-step manual computations that verify that the output is correct. 
Notice that the output length is N + M - 1 = 3 + 5 - 1 = 7.
</p>


<h3>Example 1: The sum of two independent random variables</h3>
<p>
An example of a discrete convolution in probability theory is the distribution of the sum of two independent rolls of dice.
When you sum two independent random variables, the distribution of the sum is the convolution of their individual probability distributions.
</p>
<p>A classic example is two fair six-sided dice. Let <i>X</i> and <i>Y</i> be the outcomes of the two dice. Both follow a discrete uniform distribution. The probability mass function (PMF) for a single die can be represented as a vector of probabilities for the outcomes 1 through 6. Thus,
u = {1/6, 1/6, 1/6, 1/6, 1/6, 1/6} and v = {1/6, 1/6, 1/6, 1/6, 1/6, 1/6} are the PMFs.
</p>
<p>
Let Z = X + Y be the sum of the dice faces. The random variable Z takes on values in the range 2, 3, 4, ..., 12.
The probability that Z=2 is relatively small because there is only one way ("snake eyes") for that sum to occur.
In contrast, there are six equally likely ways for the sum to be 7: 1+6, 2+5, 3+4, 4+3, 5+2, and 6+1. 
In general, if the sum of the two dice is <em>s</em>, and if <em>X=k</em>, then we know that second die has the value <em>s-k</em>.
Thus, to find the probability that <em>Z=s</em>, we must sum the probabilities that <em>X=k</em> and <em>Y=s-k</em> for <em>k</em>=1..6.
</p>
<p>
Because X and Y are independent, the probability that <em>X=k</em> and <em>Y=s-k</em> is the product
P(<em>X=k</em>)*P(<em>Y=s-k</em>). To find the complete probability that Z=s, we sum over all the ways that 
the sum <em>s</em> can occur. This leads to the formula 
<br />
<span class='MathJax_Preview'>\( P(Z = s) = \sum_{k} P(X = k) P(Y = s - k) \)</span><script type='math/tex'> P(Z = s) = \sum_{k} P(X = k) P(Y = s - k) </script>
<br />
The right side of the formula is the k_th element of a convolution.
Thus, to find the probability distribution of the sum of two dice, you can convolve 
the PMFs for each die.
</p>
<p>
You can use the DFCONV function in IML to find the distribution of the sum of two six-sided dice, 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;">/* Example: Discrete convoution of rolling two fair six-sided dice */</span>
<span style="color: #000080; font-weight: bold;">proc iml</span>;
prob_d6 = <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: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span> / <span style="color: #2e8b57; font-weight: bold;">6</span>;            <span style="color: #006400; font-style: italic;">/* PMF of six-side fair die */</span>
prob_2dice = dfconv<span style="color: #66cc66;">&#40;</span>prob_d6, prob_d6<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* PMF of sum of two dice */</span>
print prob_2dice<span style="color: #66cc66;">&#91;</span>r=<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span>:<span style="color: #2e8b57; font-weight: bold;">12</span><span style="color: #66cc66;">&#41;</span> F=FRACT12.<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/dfconv2.png" alt="" width="93" height="336" class="alignnone size-full wp-image-60384" srcset="https://blogs.sas.com/content/iml/files/2026/09/dfconv2.png 93w, https://blogs.sas.com/content/iml/files/2026/09/dfconv2-83x300.png 83w" sizes="(max-width: 93px) 100vw, 93px" />

<p>
The PMF for each die has six elements. Thus, 
the convolution is a vector of length <i>6 + 6 - 1 = 11</i>, which corresponds to the sums 2-12.
The PMF of the sum is the familiar triangular distribution, which peak at the sum of 7 with a probability of 6/36 or 1/6.
</p>
<p>
If you want the sum of three dice, you can convolve the distribution of the sum with the distribution of another fair die,
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;">/* Want three dice? Convolve the probability distribution for a six-sided 
   die with the distribution for two independent dice. */</span>
prob_3dice = dfconv<span style="color: #66cc66;">&#40;</span>prob_d6, prob_2dice<span style="color: #66cc66;">&#41;</span>;
print prob_3dice<span style="color: #66cc66;">&#91;</span>r=<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">3</span>:<span style="color: #2e8b57; font-weight: bold;">18</span><span style="color: #66cc66;">&#41;</span> F=FRACT12.<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/dfconv3.png" alt="" width="91" height="472" class="alignnone size-full wp-image-60381" srcset="https://blogs.sas.com/content/iml/files/2026/09/dfconv3.png 91w, https://blogs.sas.com/content/iml/files/2026/09/dfconv3-58x300.png 58w" sizes="(max-width: 91px) 100vw, 91px" />

<p>
You can continue this process as many times as you like. 
You can compare the result to <a href="https://blogs.sas.com/content/iml/2024/08/26/formula-sum-of-dice.html">the classical formula for rolling n six-sided dice</a>.
If the dice are not fair, you can replace the vectors u and v with other vectors that specify the probability for each face.
</p>


<h3>Example 2: Moving averages in time series</h3>
<p>
A second application of convolution is smoothing a sequence of data points by using a moving window analysis. 
When you apply a moving average, you convolve a short sequence (the filter or kernel) with a longer data sequence.
I have previously discussed <a href="https://blogs.sas.com/content/iml/2016/01/25/moving-average.html">how to compute a moving average</a> and 
<a href="https://blogs.sas.com/content/iml/2016/01/27/moving-average-in-sas.html">how to compute a moving average in SAS</a> by using PROC EXPAND or SAS IML functions. 
</p>
<p>
In the previous post, I use PROC EXPAND to find two different (backward) moving averages of the monthly price of IBM stock in the late 1990s and early 2000s. The following SAS statements smooth 120 prices and create a graph that overlays the scatter plot, a moving average, and a weighted moving average. See the previous article for an explanation and discussion.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Example from https://blogs.sas.com/content/iml/2016/01/27/moving-average-in-sas.html */</span>
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Monthly IBM Stock Price&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sort</span> <span style="color: #000080; font-weight: bold;">data</span>=sashelp.stocks<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">where</span>=<span style="color: #66cc66;">&#40;</span>STOCK=<span style="color: #a020f0;">'IBM'</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">rename</span>=<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">Date</span>=t <span style="color: #0000ff;">Close</span>=y<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> 
          out=Series<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">keep</span>=t y<span style="color: #66cc66;">&#41;</span>;
  <span style="color: #0000ff;">where</span> <span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'01JAN1996'</span>d &lt;= t &lt;= <span style="color: #a020f0;">'31DEC2005'</span>d<span style="color: #66cc66;">&#41;</span>;
  <span style="color: #0000ff;">by</span> t;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* create three moving average curves. Use the TRIM option to remove 
   edge effects and compute only the &quot;middle&quot; of the smoother. */</span>
<span style="color: #000080; font-weight: bold;">proc expand</span> <span style="color: #000080; font-weight: bold;">data</span>=Series out=out method=none;
   id t;
   convert y = MA   / transout=<span style="color: #66cc66;">&#40;</span>movave <span style="color: #2e8b57; font-weight: bold;">5</span> <span style="color: #0000ff;">trim</span> <span style="color: #2e8b57; font-weight: bold;">5</span><span style="color: #66cc66;">&#41;</span>;          <span style="color: #006400; font-style: italic;">/* backward MA(5) */</span>
   convert y = WMA  / transout=<span style="color: #66cc66;">&#40;</span>movave<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #2e8b57; font-weight: bold;">4</span> <span style="color: #2e8b57; font-weight: bold;">5</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">trim</span> <span style="color: #2e8b57; font-weight: bold;">5</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* backward WMA   */</span>
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=out cycleattrs;
   scatter <span style="color: #0000ff;">x</span>=t y=y;
   series <span style="color: #0000ff;">x</span>=t y=MA   / name=<span style="color: #a020f0;">'MA'</span>   legendlabel=<span style="color: #a020f0;">&quot;MA(5)&quot;</span>;
   series <span style="color: #0000ff;">x</span>=t y=WMA  / name=<span style="color: #a020f0;">'WMA'</span>  legendlabel=<span style="color: #a020f0;">&quot;WMA(1,2,3,4,5)&quot;</span>;
   keylegend <span style="color: #a020f0;">'MA'</span> <span style="color: #a020f0;">'WMA'</span>;
   xaxis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span> grid;
   yaxis <span style="color: #0000ff;">label</span>=<span style="color: #a020f0;">&quot;Closing Price&quot;</span> 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/dfconv4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/dfconv4.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60390" srcset="https://blogs.sas.com/content/iml/files/2026/09/dfconv4.png 640w, https://blogs.sas.com/content/iml/files/2026/09/dfconv4-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>


<p>PROC EXPAND has <a href="https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/etsug/etsug_expand_details19.htm">various options to deal with averages at the beginning and end of the sequence</a>, where there are not enough points to perform a full convolution. By default, it adjusts the width of the moving window. To avoid a discussion of edge effects, I used the TRIM option to output missing values for the first and last five averaged values. Thus, the graph shows smoothers that do not predict values for the first five and the last five observations. 
I will do the same after convolving the window and the data. In this way, I avoid a distracting discussion about why the default behavior of PROC EXPAND deviates from the mathematical definition of a convolution.
</p>
<p>
Let's perform the moving average computation in SAS IML by using a convolution. We want the filter elements to sum to unity,
A simple backward moving average with a window width of 5 corresponds to a filter with values u={1,1,1,1,1}/5.
The backward weighted moving average corresponds to u={5,4,3,2,1}/15.
Notice that the order of the elements in the weighted kernel are reversed from the PROC EXPAND order.
Why? Well, the mathematical definition of a convolution flips the kernel shape (that is, it uses u(k-j) to compute the k_th smoothed point). 
We therefore need to manually reverse the elements in u before calling the DFCONV function so that the output agrees with the
backward moving average from PROC EXPAND.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* perform a similar computation by using convolution */</span>
<span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* read the data into v */</span>
use Series;  read all <span style="color: #0000ff;">var</span> <span style="color: #a020f0;">&quot;y&quot;</span> <span style="color: #0000ff;">into</span> v;  <span style="color: #0000ff;">close</span>;
&nbsp;
u = <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: #2e8b57; font-weight: bold;">5</span>;   <span style="color: #006400; font-style: italic;">/* MA(5) */</span>
MA = dfconv<span style="color: #66cc66;">&#40;</span>u, v<span style="color: #66cc66;">&#41;</span>;
<span style="color: #006400; font-style: italic;">/* reverse the filter elements to match the PROC EXPAND output for a backward MA */</span>   
u = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">5</span>,<span style="color: #2e8b57; font-weight: bold;">4</span>,<span style="color: #2e8b57; font-weight: bold;">3</span>,<span style="color: #2e8b57; font-weight: bold;">2</span>,<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span> / <span style="color: #2e8b57; font-weight: bold;">15</span>;  <span style="color: #006400; font-style: italic;">/* WMA(1,2,3,4,5) */</span>
WMA = dfconv<span style="color: #66cc66;">&#40;</span>u, v<span style="color: #66cc66;">&#41;</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* We want to compare with PROC EXPAND. The PROC
   1) outputs N elements.
   2) has special handling for the first and last N obs.
*/</span>
M = nrow<span style="color: #66cc66;">&#40;</span>u<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">N</span> = nrow<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
MA  = MA<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span>:<span style="color: #0000ff;">N</span><span style="color: #66cc66;">&#93;</span>;    <span style="color: #006400; font-style: italic;">/* keep only N elements */</span>
WMA = WMA<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span>:<span style="color: #0000ff;">N</span><span style="color: #66cc66;">&#93;</span>;
&nbsp;
firstObs = <span style="color: #2e8b57; font-weight: bold;">1</span>:M;
lastObs  = <span style="color: #66cc66;">&#40;</span>N-M+<span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>:<span style="color: #0000ff;">N</span>; 
MA <span style="color: #66cc66;">&#91;</span>firstObs||lastObs<span style="color: #66cc66;">&#93;</span> = .;  <span style="color: #006400; font-style: italic;">/* set the first/last obs to missing */</span>
WMA<span style="color: #66cc66;">&#91;</span>firstObs||lastObs<span style="color: #66cc66;">&#93;</span> = .;
&nbsp;
<span style="color: #0000ff;">create</span> IML_MA <span style="color: #0000ff;">var</span> <span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'MA'</span> <span style="color: #a020f0;">'WMA'</span><span style="color: #66cc66;">&#125;</span>;  append;  <span style="color: #0000ff;">close</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">proc compare</span> base=out<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">keep</span>=MA WMA<span style="color: #66cc66;">&#41;</span> compare=IML_MA 
             method=absolute criterion=1E-12;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">                                       Observation Summary                                        
&nbsp;
                                  Observation      Base  Compare                                  
&nbsp;
                                  First Obs           1        1                                  
                                  Last  Obs         120      120                                  
&nbsp;
                 Number of Observations in Common: 120.                                           
                 Total Number of Observations Read from WORK.OUT: 120.                            
                 Total Number of Observations Read from WORK.IML_MA: 120.                         
&nbsp;
                 Number of Observations with Some Compared Variables Unequal: 0.                  
                 Number of Observations with All Compared Variables Equal: 120.</pre></td></tr></table></div>




<p>
The IML values (computed by using a convolution) agree with the PROC EXPAND values to within 1E-12. (In fact, the largest difference
for this example is 5.68E-14.) If we had not used the TRIM option in PROC EXPAND, then there would be differences in the first few observations because PROC EXPAND uses a varying-width kernel near the beginning and end of the data series.
</p>

<h3>Summary</h3>
<p>
This article discusses the mathematical definition of a discrete convolution operator. 
In SAS, you can use the DFCONV function in SAS IML to convolve two vectors of arbitrary lengths. 
The result is a new longer vector. The convolution operator is illustrated by using two examples: 
the distribution of the sum of two six-sided dice, and the smoothing of a time series by using a moving average.
</p>



<h3>Appendix A: Manual computation of a discrete convolution</h3>
<p>
This appendix shows a simple manual computation of discrete convolution for two vectors. 
It is presented so that you can compare the output of the DFCONV function with a function 
that directly implements the convolution formula.
</p><p>
Suppose you define a vector, u={1,2,3}, which has M=3 elements, 
and a vector, v={4,5,6,7,8}, which has N=5 elements.
If you want to compute their convolution, y=u*v, you can use the following manual computations.
These computations assume zero-based indexing. For this problem, the convolution has M+N-1 = 7 elements:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">y[0] = u[0]v[0] = 1*4 = 4
y[1] = u[0]v[1] + u[1]v[0] = (1*5) + (2*4) = 13
y[2] = u[0]v[2] + u[1]v[1] + u[2]v[0] = (1*6) + (2*5) + (3*4) = 28
y[3] = u[0]v[3] + u[1]v[2] + u[2]v[1] = (1*7) + (2*6) + (3*5) = 34
y[4] = u[0]v[4] + u[1]v[3] + u[2]v[2] = (1*8) + (2*7) + (3*6) = 40
y[5] = u[1]v[4] + u[2]v[3] = (2*8) + (3*7) = 37
y[6] = u[2]v[4] = 3*8 = 24</pre></td></tr></table></div>



<p>
Thus, the convolution y=u*v is a new vector, 
y = {4,13,28,34,40,37,24}.
</p>
<p>
There are two issues when turning this into a computation. First, the IML language 
uses one-based indexing, not zero-based. Second, a naive implementation would use
a pair of nested loops and perform scalar multiplication and addition, which is 
inefficient in a vectorized language. A better implementation is to use 
a dot product to perform the sums of the products of subsequences. 
After the first few elements of the vector v, the dot products will always 
use subsequences of length M, which is the length of u.
</p><p>
The following computation implements the definition of the 
discrete convolution formula. 
</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;">/* y = conv(u,v) convolves column vectors u and v. 
   Algebraically, convolution is the same operation as 
   multiplying the polynomials whose coefficients are the 
   elements of u and v. 
*/</span>
start conv_manual<span style="color: #66cc66;">&#40;</span>u,v<span style="color: #66cc66;">&#41;</span>;
   m = nrow<span style="color: #66cc66;">&#40;</span>u<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">n</span> = nrow<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
   ny = m + <span style="color: #0000ff;">n</span> - <span style="color: #2e8b57; font-weight: bold;">1</span>;
   y = j<span style="color: #66cc66;">&#40;</span>ny, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">0</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
   <span style="color: #0000ff;">do</span> k = <span style="color: #2e8b57; font-weight: bold;">1</span> to ny;
      idx = <span style="color: #0000ff;">max</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>, k+<span style="color: #2e8b57; font-weight: bold;">1</span>-<span style="color: #0000ff;">n</span><span style="color: #66cc66;">&#41;</span>:<span style="color: #0000ff;">min</span><span style="color: #66cc66;">&#40;</span>k,m<span style="color: #66cc66;">&#41;</span>; 
      y<span style="color: #66cc66;">&#91;</span>k<span style="color: #66cc66;">&#93;</span> = u<span style="color: #66cc66;">&#91;</span>idx<span style="color: #66cc66;">&#93;</span>` <span style="color: #006400; font-style: italic;">* v[k + 1 - idx];</span>
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span>y<span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* test the computation */</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: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#125;</span>;
v = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">4</span>,<span style="color: #2e8b57; font-weight: bold;">5</span>,<span style="color: #2e8b57; font-weight: bold;">6</span>,<span style="color: #2e8b57; font-weight: bold;">7</span>,<span style="color: #2e8b57; font-weight: bold;">8</span><span style="color: #66cc66;">&#125;</span>;
y1 = conv_manual<span style="color: #66cc66;">&#40;</span>u,v<span style="color: #66cc66;">&#41;</span>;
<span style="color: #006400; font-style: italic;">/* compare to the built-in IML function */</span>
y2 = dfconv<span style="color: #66cc66;">&#40;</span>u,v<span style="color: #66cc66;">&#41;</span>;
print y1 y2;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/dfconv5.png" alt="" width="73" height="234" class="alignnone size-full wp-image-60378" />

<p>
Notice the statement, <tt>idx = max(1, k+1-n):min(k,m)</tt>, which selects the indices to use for the dot product.
This statement corresponds to the summation limits in the mathematical formula earlier.
In the formula, the lower limit of summation is max(0, k-N+1) and the upper limit  is min(k, M-1). 
The IML statement adjusts these limits to account for 1-based indexing. 
</p>

<h3>Appendix B: Convolution as matrix-vector multiplication</h3>
<p>
</p><p>
Incidentally, the first time I saw a convolution operator was in a course in Linear Algebra where it was presented as an
example of a bilinear operator between two vector spaces. Accordingly, you can represent a convolution as a matrix-vector
multiplication where the matrix T=T(v) is a Toeplitz-type
matrix of the appropriate dimension and padded with zeros. 
In honor of my now-deceased Cornell math professor, Keith Dennis, I'll show how to construct the matrix 
and use it to compute a convolution in IML:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">ny = nrow(v) + nrow(u) - 1;
Tv = j(ny, nrow(u), 0);
do i = 1 to nrow(u);
   Tv[i:(i+nrow(v)-1),i] = v;    /* create a Toeplitz-type matrix by copying v into columns */
end;
y3 = Tv*u;                       /* matrix-vector multiplication */
print Tv, y3;</pre></td></tr></table></div>




<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/dfconv6.png" alt="" width="70" height="466" class="alignnone size-full wp-image-60375" srcset="https://blogs.sas.com/content/iml/files/2026/09/dfconv6.png 70w, https://blogs.sas.com/content/iml/files/2026/09/dfconv6-45x300.png 45w" sizes="(max-width: 70px) 100vw, 70px" />

<p>
In practice, there is no advantage to construct a large matrix and perform matrix-vector multiplication.
You can save RAM memory
and compute a series of dot products instead.
Nevertheless, it's a cool result!
</p><p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/10/05/intro-convolution.html">What is convolution and why is it important in statistics?</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/10/05/intro-convolution.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/09/dfconv4-150x150.png" />
	</item>
		<item>
		<title>What is Owen&#039;s T function?</title>
		<link>https://blogs.sas.com/content/iml/2026/09/28/owen-t-function.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/09/28/owen-t-function.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 28 Sep 2026 09:21:17 +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=60261</guid>

					<description><![CDATA[<p>While researching a topic in probability, I encountered a special mathematical function that I had not previously known about. The function is called Owen's T function (Owen, 1956, Ann. Math. Stat.). It is useful for computing multivariate probabilities, for defining the skew-normal distribution, and for computing probabilities associated with certain [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/28/owen-t-function.html">What is Owen&#039;s T function?</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
While researching a topic in probability, I encountered a special mathematical function that I had not previously known about. The function is called Owen's T function (<a href="https://www.jstor.org/stable/2237196">Owen, 1956, Ann. Math. Stat.</a>).
It is useful for computing multivariate probabilities, for defining the skew-normal distribution, and for computing probabilities associated with certain hypothesis tests.
This article shows how to compute and visualize Owen's T function in SAS. I will show applications of Owen's T function in a subsequent article.
</p>
<p>
Owen's T function is defined as a one-dimensional integral, so this article uses the QUAD subroutine in SAS IML to evaluate it. However, it is worth mentioning that 
<a href="https://ideas.repec.org/a/jss/jstsof/v005i05.html">Patefield, M. and Tandy, D. (2000, <em>JSS</em>)</a> provide a 
faster and more accurate algorithm. Their algorithm is more complex. It evaluates the function
by selecting from among six different numerical methods, depending on the specified values of (x, &alpha;).
</p>

<h3>The definition of Owen's T function</h3>
<p>
The definition of Owen's T function, T(x, &alpha;), uses &alpha; as the upper limit of an integral and <em>X</em> as a parameter to the integrand:
<br />
<span class='MathJax_Preview'>\(
T(x, \alpha) = \frac{1}{2\pi} \int_0^\alpha \frac{e^{-\frac{x^2}{2}(1+t^2)}}{1+t^2} dt
\)</span><script type='math/tex'>
T(x, \alpha) = \frac{1}{2\pi} \int_0^\alpha \frac{e^{-\frac{x^2}{2}(1+t^2)}}{1+t^2} dt
</script>
</p>
<p>
From this definition, it is clear that T is an even function in x and an odd function in &alpha;:
</p>
<ul>
<li>T is symmetric in x: T(-x, &alpha;) = T(x, &alpha;)</li>
<li>T is anti-symmetric in &alpha;: T(x, -&alpha;) = -T(x, &alpha;)</li>
</ul>
<p>
It is less obvious that Owen's T function simplifies if x=0 or &alpha;=1:
</p>
<ul>
<li> T(0,&alpha;) = 1/(2 &pi;) tan<sup>-1</sup>(&alpha;) </li>
<li> T(x,1) = 1/2 * &Phi;(x) (1 - &Phi;(x)), where &Phi;(x) is the standard univariate normal CDF.
</li>
</ul>


<h3>Define Owen's T function in SAS</h3>
<p>
<a href="https://blogs.sas.com/content/iml/2011/05/06/how-to-numerically-integrate-a-function-in-sas.html">The QUAD subroutine in SAS IML is a general-purpose integration routine</a>. 
The domain of integration in the formula is [0, &alpha;]. However, &alpha; can be negative, so
it might be necessary to <a href="https://blogs.sas.com/content/iml/2014/08/04/reversing-the-limits-of-integration.html">reverse the limits of integration</a>. 
To handle that possibility, 
I specify the domain of integration as [0, |&alpha;|] and negate the answer if &alpha; &lt; 0.
Here is one way to define Owen's T function:
</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;">/* Define the Integrand for Owen's T function:
   The QUAD subroutine passes a scalar value, t, during the integration process.
   Use the GLOBAL statement to pass the parameter value, x (via 'g_x').
*/</span>
start OwenT_Integrand<span style="color: #66cc66;">&#40;</span>t<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_x<span style="color: #66cc66;">&#41;</span>;
   t2 = <span style="color: #2e8b57; font-weight: bold;">1</span> + t##<span style="color: #2e8b57; font-weight: bold;">2</span>;
   <span style="color: #0000ff;">return</span> <span style="color: #66cc66;">&#40;</span> <span style="color: #0000ff;">exp</span><span style="color: #66cc66;">&#40;</span>-<span style="color: #2e8b57; font-weight: bold;">0.5</span> <span style="color: #006400; font-style: italic;">* g_x##2 * t2) / t2 );</span>
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Define Owen's T Function by integrating OwenT_Integrand on [0, alpha].
   Loop over the elements of x to allow vector inputs. alpha is a scalar.
*/</span>
start OwenT<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, alpha<span style="color: #66cc66;">&#41;</span> global<span style="color: #66cc66;">&#40;</span>g_x<span style="color: #66cc66;">&#41;</span>;
   pi = constant<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">&quot;pi&quot;</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">n</span> = nrow<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#41;</span>;
   y = 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>, <span style="color: #2e8b57; font-weight: bold;">1</span>, .<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #006400; font-style: italic;">/* make sure limits of integration are increasing. If alpha &lt; 0, reverse sign of answer */</span>
   limits = <span style="color: #2e8b57; font-weight: bold;">0</span> || <span style="color: #0000ff;">abs</span><span style="color: #66cc66;">&#40;</span>alpha<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">do</span> i = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff;">n</span>;
      g_x = <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>;   <span style="color: #006400; font-style: italic;">/* set parameter, 'x' */</span>
      <span style="color: #0000ff;">call</span> quad<span style="color: #66cc66;">&#40;</span>val, <span style="color: #a020f0;">&quot;OwenT_Integrand&quot;</span>, limits<span style="color: #66cc66;">&#41;</span>;
      y<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = val;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> choose<span style="color: #66cc66;">&#40;</span>alpha&gt;=<span style="color: #2e8b57; font-weight: bold;">0</span>, y/<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #006400; font-style: italic;">*pi), -y/(2*pi)) );</span> <span style="color: #006400; font-style: italic;">/* negate answer if alpha &lt; 0 */</span>
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* test evaluating the function for x in [-3,3] */</span>
<span style="color: #0000ff;">x</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;">3</span>, <span style="color: #2e8b57; font-weight: bold;">3</span>, <span style="color: #2e8b57; font-weight: bold;">6</span>/<span style="color: #2e8b57; font-weight: bold;">50</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
alpha = <span style="color: #2e8b57; font-weight: bold;">1</span>;
T = OwenT<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, alpha<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Graph of Owen's T Function: alpha=1&quot;</span>;
<span style="color: #0000ff;">call</span> series<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">x</span>, T<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>;</pre></td></tr></table></div>




<a href="https://blogs.sas.com/content/iml/files/2026/09/OwenT1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/OwenT1.png" alt="" width="480" height="360" class="alignnone size-full wp-image-60303" srcset="https://blogs.sas.com/content/iml/files/2026/09/OwenT1.png 640w, https://blogs.sas.com/content/iml/files/2026/09/OwenT1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
The plot shows the graph of Owen's T function when the parameter &alpha; is fixed at &alpha;=1 and 
the parameter x is in the interval [-3, 3].
You can see that the graph looks somewhat bell-shaped. 
</p>
<p>
The following panel of graphs shows the T(x, &alpha;) function for various values of &alpha;.
You can see that the shape of the function is similar for all &alpha; values, but the amplitude of the function depends on &alpha;. You can show analytically that the magnitude of the T function is greatest at x=0.
The height of the function at x=0 is 1/(2 &pi;) tan<sup>-1</sup>(&alpha;).
</p>

<a href="https://blogs.sas.com/content/iml/files/2026/09/OwenT2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/09/OwenT2.png" alt="" width="360" height="480" class="alignnone size-full wp-image-60309" srcset="https://blogs.sas.com/content/iml/files/2026/09/OwenT2.png 480w, https://blogs.sas.com/content/iml/files/2026/09/OwenT2-225x300.png 225w" sizes="(max-width: 360px) 100vw, 360px" /></a>

<p>
If you like 3-D graphs, the Boost C++ library contains <a href="https://www.boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/owens_t.html">a graph of T(x, &alpha;)</a> over a rectangular domain.
</p>

<h3>Summary</h3>
<p>
Owen's T function is defined as a one-dimensional integral with two parameters. This article uses the QUAD subroutine in SAS IML to evaluate the integral for any parameter values. A subsequent article will discuss applications of Owen's T function in probability and statistics.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/09/28/owen-t-function.html">What is Owen&#039;s T function?</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/28/owen-t-function.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/09/OwenT1-150x150.png" />
	</item>
		<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 loading="lazy" 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 loading="lazy" 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 loading="lazy" 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#comments</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>2</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>
	</channel>
</rss>
