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


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


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

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


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


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


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


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


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


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


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


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

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


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


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


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


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


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


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

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

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


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


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


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


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


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


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

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


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


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


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


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


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


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


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


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

					<description><![CDATA[<p>The kernel density estimate (KDE) is a powerful tool for estimating the density of univariate data. The KDE is a flexible model. For example, it can fit data distributions that are multimodal or have long tails. It is nonparametric, which means that you do not need to assume any form [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/13/kde-positive.html">Kernel density estimates for positive data</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>
The kernel density estimate (KDE) is a powerful tool for estimating the density of univariate data.
The KDE is a flexible model. For example, it can fit data distributions that are multimodal or have long tails.
It is nonparametric, which means that 
you do not need to assume any form for the distribution of the data.
In contrast, parametric estimates (such as normal or lognormal distributions) impose 
constraints on the shape of the data distribution.
</p><p>
However, the KDE's flexibility comes at a cost. If the underlying distribution is known to have certain properties, 
the KDE does not necessarily preserve those properties.
A property that arises often in practice is <em>positivity</em>. 
Many real-world measurements are strictly positive, 
such as mass, length, and many health-related clinical lab values.
If you have a distribution of lengths, and you fit a KDE to the data, it is disconcerting (and wrong!) to 
display a density estimate that predicts positive density for negative lengths!
</p>
<p>
There are two ways to deal with this problem. 
This article shows how to use SAS to truncate the density estimate 
by setting the density to zero when the variable is negative. 
A subsequent article shows how to log-transform the data, fit a KDE on the log scale, and back-transform the results in SAS. 
</p>

<h3>Fit a KDE to positive data</h3>
<p>
The following data set shows the position (in millimeters) of a rivet 
relative to a bulkhead. The distance is a positive quantity.
For convenience, I assign the name of the data sets and the variables to macro variables.
The remainder of the examples use only the macro variables, which makes it easy for you to
run the code on your own data.
</p>


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




<p> You can use either PROC UNIVARIATE or PROC KDE to fit a kernel density estimate.
I will use PROC KDE because, as we will see in the next section, it supports options to truncate
the KDE.
The following call fits the KDE to the data and visualizes the result. The UNISTATS option displays descriptive statistics for the
data and shows the bandwidth chosen for the fit:
</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 kde</span> <span style="color: #000080; font-weight: bold;">data</span>=&amp;<span style="color: #0000ff;">DSName</span>;
   univar &amp;<span style="color: #0000ff;">varName</span> / unistats;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc1.png" alt="" width="214" height="405" class="alignnone size-full wp-image-59369" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc1.png 214w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc1-159x300.png 159w" sizes="(max-width: 214px) 100vw, 214px" />
<br />

<figure id="attachment_59372" aria-describedby="caption-attachment-59372" style="width: 480px" class="wp-caption alignnone"><a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc2.png" alt="" width="480" height="360" class="size-full wp-image-59372" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc2.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc2-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a><figcaption id="caption-attachment-59372" class="wp-caption-text">Original Untruncated KDE</figcaption></figure>

<p>
I have highlighted a few entries in the output tables. Although the range of the data is [1.5, 21.28], 
the Controls table shows that the range of the KDE is [-4.99, 27.77].
The graph of the KDE curve confirms this fact. 
The KDE extends about three bandwidths past the extremes of the data, and the kernel bandwidth
for these data is 2.13.
However, it does not make sense to estimate the 
density to be nonzero for Position &lt; 0 because Position must be a positive quantity.
</p>

<p>
PROC KDE provides options that enable you to truncate the density estimate in two ways:
</p>
<ul>
<li>Truncate the KDE to the range of the data by using the TRUNCATE option.</li>
<li>Truncate the KDE to an arbitrary range by using the GRIDL= option to specify a lower limit and/or
the GRIDU= option to specify an upper limit.
</li></ul>

<p>
It is somewhat unfortunate that PROC KDE does not have an option to display the histogram and KDE on the percentage scale.
Most graphs in this article are shown on the frequency scale. You can <a href="https://blogs.sas.com/content/iml/2024/06/19/scale-density-curve-histogram.html">rescale the graphs to display them on the percent or density scale.</a>
The last graph in this article is shown on the percentage scale.
</p>


<h3>Truncate the density into the data range</h3>
<p>
The following call uses the TRUNCATE option to restrict the KDE to the range of the data:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* truncate KDE to data range */</span>
<span style="color: #000080; font-weight: bold;">proc kde</span> <span style="color: #000080; font-weight: bold;">data</span>=&amp;<span style="color: #0000ff;">DSName</span>;
   univar &amp;<span style="color: #0000ff;">varName</span> / truncate out=KDE_truncData; <span style="color: #006400; font-style: italic;">/* save KDE to data set for later analysis */</span>
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<figure id="attachment_59390" aria-describedby="caption-attachment-59390" style="width: 480px" class="wp-caption alignnone"><a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc3.png" alt="" width="480" height="360" class="size-full wp-image-59390" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc3.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a><figcaption id="caption-attachment-59390" class="wp-caption-text">Truncate KDE to Data Range</figcaption></figure>

<p>
Notice that the KDE is truncated on the interval [1.5, 21.28], which is the range of the data.
</p>

<h3>Truncate the density onto the positive half-line</h3>

<p>
The Position variable cannot be negative, but there is no intrinsic reason why it cannot be smaller than 1.5. 
There is also no obvious reason to bound the density from above.
An alternative way to truncate the density is to use the GRIDL=0 option to truncate the density to strictly positive values.
</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 kde</span> <span style="color: #000080; font-weight: bold;">data</span>=&amp;<span style="color: #0000ff;">DSName</span>;
   univar &amp;<span style="color: #0000ff;">varName</span> / gridl=<span style="color: #2e8b57; font-weight: bold;">0</span> out=KDE_trunc0; <span style="color: #006400; font-style: italic;">/* save KDE to data set for later analysis */</span>
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>




<figure id="attachment_59387" aria-describedby="caption-attachment-59387" style="width: 480px" class="wp-caption alignnone"><a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc4.png" alt="" width="480" height="360" class="size-full wp-image-59387" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc4.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc4-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a><figcaption id="caption-attachment-59387" class="wp-caption-text">Truncate KDE Below Zero</figcaption></figure>

<p>
This KDE is restricted to positive values. It is unbounded above, but the density is exactly zero for negative values of the Position variable.
If you have an upper limit, you can set it by using the GRIDU= option. For example, if you are plotting the density of student scores on an exam that has 100 as a perfect score, you could use <code class="preserve-code-formatting">GRIDL=0 GRIDU=100</code> to bound the KDE to the range [0, 100].
</p>

<h3>Limitations of the truncation method</h3>
<p>
There are two problems with truncating a KDE:
</p>
<ul>
<li>On a probability scale, the area under a true density must integrate to 1.
If you tell PROC KDE to truncate the KDE, it does so by throwing away some area.
Thus, area is less than 1 for the restricted KDE.</li>
<li>Even if the estimate is rescaled, the KDE underestimates the expected value of distribution near 0 (Silverman, 1986, Section 2.10).
</li>
</ul>

<p>
You can rescale to the density to correct for the first problem. In the previous sections, I wrote the values of the KDE to the data sets 
KDE_truncData
and
KDE_trunc0, respectively.
The following macro computes the area under these curves by using <a href="https://blogs.sas.com/content/iml/2011/07/08/the-area-under-a-density-estimate-curve-nonparametric-estimates.html">a trapezoidal approximation to the area</a>.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* the area under a density estimate should be 1. See
   https://blogs.sas.com/content/iml/2011/07/08/the-area-under-a-density-estimate-curve-nonparametric-estimates.html
   The following macro computes the area under a KDE estimate that is created by PROC KDE and written
   to the 'DSName' data set by using the OUT= option on the UNIVAR statement.
   In the data set the x variable is named 'value', and the 'density' variable is f(x).
   */</span>
<span style="color: #0000ff;">%macro</span> AreaUnderKDE<span style="color: #66cc66;">&#40;</span>DS<span style="color: #66cc66;">&#41;</span>;
<span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff;">_null_</span>;
   <span style="color: #0000ff;">set</span> <span style="color: #0000ff; font-weight: bold;">&amp;DS</span> <span style="color: #0000ff;">end</span>=EOF;
   dx = <span style="color: #0000ff;">dif</span><span style="color: #66cc66;">&#40;</span>value<span style="color: #66cc66;">&#41;</span>;
   meanY = <span style="color: #66cc66;">&#40;</span>density + <span style="color: #0000ff;">lag</span><span style="color: #66cc66;">&#40;</span>density<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> / <span style="color: #2e8b57; font-weight: bold;">2</span>;
   Area + dx<span style="color: #006400; font-style: italic;">*meanY;</span>
   <span style="color: #0000ff;">if</span> EOF <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">call</span> symputx<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'Area'</span>, Area, <span style="color: #a020f0;">&quot;G&quot;</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* create global macro */</span>
   <span style="color: #0000ff;">end</span>;
<span style="color: #000080; font-weight: bold;">run</span>;
<span style="color: #0000ff;">%mend</span>;
&nbsp;
%AreaUnderKDE<span style="color: #66cc66;">&#40;</span>KDE_truncData<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">%put</span> <span style="color: #0000ff;">Trunc</span> <span style="color: #000080; font-weight: bold;">Data</span> &amp;=Area;
%AreaUnderKDE<span style="color: #66cc66;">&#40;</span>KDE_trunc0<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">%put</span> <span style="color: #0000ff;">Trunc</span> to <span style="color: #2e8b57; font-weight: bold;">0</span> &amp;=Area;</pre></td></tr></table></div>





<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">Trunc Data AREA=0.9031115838
Trunc to 0 AREA=0.97522813</pre></td></tr></table></div>




<p>
The log shows that the area is less than 1 for both KDE curves.
This can be a problem if you intend to use the KDE to estimate probabilities.
However, it is simple enough to rescale the truncated density by 
dividing it by its area:
</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 'density' is a variable that does not integrates to 1,
   you can renormalize by dividing by the Area under the density curve. */</span>
<span style="color: #0000ff;">%macro</span> Renormalize<span style="color: #66cc66;">&#40;</span>DS, Area<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff; font-weight: bold;">&amp;DS</span>;
      <span style="color: #0000ff;">set</span> <span style="color: #0000ff; font-weight: bold;">&amp;DS</span>;
      density = density / <span style="color: #0000ff; font-weight: bold;">&amp;Area</span>;
   <span style="color: #000080; font-weight: bold;">run</span>;
<span style="color: #0000ff;">%mend</span>;
&nbsp;
%Renormalize<span style="color: #66cc66;">&#40;</span>KDE_trunc0, <span style="color: #0000ff; font-weight: bold;">&amp;Area</span><span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>




<p>The following graph overlays the renormalized truncated KDE on a histogram and fringe plot of the data. 
The KDE is truncated at 0 because the measured quantity must be positive. The graph was created by using the 
<a href="https://blogs.sas.com/content/iml/2025/10/13/high-low-emulate-histogram.html">%EmulateHistogram macro</a>
and the process described in the article, 
<a href="https://blogs.sas.com/content/iml/2025/10/20/overlay-curves-histogram.html">"Overlay multiple custom density curves on a histogram in SAS."</a>
</p>

<figure id="attachment_59402" aria-describedby="caption-attachment-59402" style="width: 640px" class="wp-caption alignnone"><a href="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc5.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc5.png" alt="" width="480" height="360" class="size-full wp-image-59402" srcset="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc5.png 640w, https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc5-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a><figcaption id="caption-attachment-59402" class="wp-caption-text">Truncated and Renormalized KDE</figcaption></figure>

<p>
The second issue (underestimating the KDE near the boundary) does not have a simple solution. Some researchers suggest reflecting the data 
across x=0 before fitting the KDE. I don't want to say more about that method here, but <a href="https://blogs.sas.com/content/iml/2012/04/06/creating-a-periodic-smoother.html">I have used it previously to create smoothers of periodic data.</a>
</p>

<h3>Summary</h3>
<p>
If you naively compute a kernel density estimate for data that are inherently positive, the KDE might estimate density 
for negative values of the variable. 
PROC KDE in SAS provides two methods for correcting this problem:
</p>
<ul>
<li>The TRUNCATE option on the UNIVAR statement truncates the KDE to the range of the data.</li>
<li>The GRIDL=L and GRIDU=U options truncate the KDE to the range [L, U]. For example, you can use GRIDL=0 to truncate the KDE onto the positive axis.</li>
</ul>

<p>
Technically, if you truncate the KDE, you should renormalize it so that the area under the curve is unity. 
This article includes two macros (%AreaUnderKDE and %Renormalize) that you can use for that purpose.
I do not deal with the fact that the truncated KDE underestimates the expected density near the boundary.
In a subsequent article, I present an alternative method: log-transforming the data.
</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/13/kde-positive.html">Kernel density estimates for positive data</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/iml">The DO Loop</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/iml/2026/07/13/kde-positive.html/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/07/KDEtrunc5-150x150.png" />
	</item>
		<item>
		<title>An alternative Pareto chart in SAS</title>
		<link>https://blogs.sas.com/content/iml/2026/07/06/alt-pareto-chart.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/07/06/alt-pareto-chart.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 06 Jul 2026 09:27:25 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Statistical Graphics]]></category>
		<category><![CDATA[Statistical Programming]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/iml/?p=38138</guid>

					<description><![CDATA[<p>Pareto charts are used by quality engineers to visually display a set of causes that produce defects in a manufacturing process. The chart is based on the famous Pareto Principle (the 80/20 rule), which states that 20% of the causes often produce 80% of the defects. If you focus on [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/06/alt-pareto-chart.html">An alternative Pareto chart 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>
Pareto charts are used by quality engineers to visually display a set of causes that produce defects in a manufacturing process.
The chart is based on the famous Pareto Principle (the 80/20 rule),
which states that 20% of the causes often produce 80% of the defects. 
If you focus on the most common causes, you can quickly reduce your defect count.
</p>
<p>
This article describes an alternative to 
<a href="https://blogs.sas.com/content/iml/2026/06/22/pareto-charts-sas.html">the standard Pareto chart</a>
and shows how to create the alternative version in SAS. 
</p>

<h3>A problem with the standard Pareto chart</h3>

<a href="https://blogs.sas.com/content/iml/files/2026/06/ParetoChart2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/06/ParetoChart2.png" alt="" width="320" height="240" class="alignright size-full wp-image-59158" srcset="https://blogs.sas.com/content/iml/files/2026/06/ParetoChart2.png 640w, https://blogs.sas.com/content/iml/files/2026/06/ParetoChart2-300x225.png 300w" sizes="(max-width: 320px) 100vw, 320px" /></a>

<p>
Not every situation obeys the 80-20 rule. For your process, you might find that 80% of the defects are produced by 33% of the causes. 
Or, maybe 75% of the defects are produced by 10% of the causes.
If your process is modeled by a Pareto distribution (a specific power-law distribution),
you can <a href="https://blogs.sas.com/content/iml/2018/11/05/fit-pareto-distribution-sas.html">use SAS to fit your data to a Pareto distribution</a>. However, most practitioners do not do that. They just display a bar chart of the causes where the bars are displayed in descending order and then 
try to address the most frequent causes.  For example, if a quality engineer looks at the Pareto chart to the right, she might decide to address the "contamination" cause first, followed by the "oxide defect" issue. Together, these two categories account for 67.5% of the defects.
</p>
<p>
But there is a fundamental problem with this strategy.  In small samples, the empirical frequencies in the data might be 
much different than the underlying probabilities for the process.
Thus, the "most frequent" category in the data might not be the most probable cause of the defects.
It would be nice to know whether the tall bars to the left 
just happen to be tall because of random variation or whether they are actually the most probable categories
and warrant your attention.
</p><p>
In a short three-page paper, <a href="https://doi.org/10.1198/000313006X152243">Wilkinson (2006, TAS, "Revising the Pareto Chart")</a>
proposes an alternative to the Pareto chart that uses the idea of statistical significance to 
give additional insight into the tallest bars.
The Wilkinson Pareto chart provides <em>acceptance intervals</em> that indicate 
whether each category appears more often than would be expected if all categories are equally probable. 
This enables you to assess whether the tallest bars are "sufficiently tall" and are, in fact, more 
probable than the other bars.
</p>

<h3>A Pareto chart with acceptance intervals</h3>
<p>
If you have a license for SAS/QC software, you can create Wilkinson's alternative Pareto chart by using the CHARTTYPE=INTERVALS option on the VBAR statement in PROC PARETO. 
<a href="https://blogs.sas.com/content/iml/2026/06/22/pareto-charts-sas.html">A previous article showed how to create two standard Pareto charts in SAS.</a>  Let's analyze the same data set, which contains the causes of 40 randomly selected defective components in a manufacturing process.
The following DATA step defines the data. The call to PROC PARETO creates Wilkinson's acceptance-interval chart:
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Modification of PROC PARETO example data */</span>
<span style="color: #000080; font-weight: bold;">data</span> Failure;
<span style="color: #0000ff;">INFILE</span> DATALINES delimiter=<span style="color: #a020f0;">','</span>;
<span style="color: #0000ff;">length</span> Cause $ <span style="color: #2e8b57; font-weight: bold;">16</span>;
<span style="color: #0000ff;">label</span> Cause = <span style="color: #a020f0;">'Cause of Failure'</span>;
<span style="color: #0000ff;">input</span> Cause @@;
datalines;
Corrosion, Oxide Defect, Contamination, Oxide Defect
Oxide Defect, Oxide Defect, Contamination, Metallization
Oxide Defect, Contamination, Contamination, Oxide Defect
Contamination, Contamination, Contamination, Corrosion
Silicon Defect, Contamination, Contamination, Contamination
Contamination, Contamination, Doping, Oxide Defect
Oxide Defect, Metallization, Contamination, Corrosion
Silicon Defect, Contamination, Corrosion, Corrosion
Metallization, Oxide Defect, Contamination, Contamination
Oxide Defect, Doping, Doping, Contamination
;
&nbsp;
<span style="color: #0000ff;">%let</span> <span style="color: #0000ff;">DSName</span> = Failure;
<span style="color: #0000ff;">%let</span> <span style="color: #0000ff;">VarName</span> = Cause;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Wilkinson variant of Pareto chart. Which categories are 
   significantly different from a uniform distribution of 
   causes? */</span>
<span style="color: #000080; font-weight: bold;">proc pareto</span> <span style="color: #000080; font-weight: bold;">data</span>=&amp;<span style="color: #0000ff;">DSName</span>;
   vbar &amp;<span style="color: #0000ff;">VarName</span> / charttype=intervals;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt1.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59303" srcset="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt1.png 640w, https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt1-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
What exactly are we looking at? <a href="https://go.documentation.sas.com/doc/en/pgmsascdc/v_073/qcug/qcug_pareto_examples13.htm">The documentation for PROC PARETO</a> states,
"the most frequently occurring problem, Contamination, occurs more frequently
than the first-ranked cause from a random sample of ... uniformly distributed causes. This result indicates
that addressing contamination problems should be given a high priority."
</p><p>
The documentation does not explain the term "first-ranked cause" or the connection to "uniformly distributed causes."
</p>

<h3>Understanding the Pareto chart with acceptance intervals</h3>
<p>
The chart shows both the observed percentages and the expected percentages under the null hypothesis that all categories are equally likely to occur.
However, the fact that a Pareto chart is sorted, introduces a complication into what would otherwise be a simple process.
</p><p>
This example has 6 categories. If the categories are equally likely, we would expect each bar in a bar chart 
to contain about 16.67% of the defects.
In the observed sample, the most frequent category (Contamination) contains 42.5% of the defects. Intuitively, we suspect that 42.5% is so much greater than 16.67% that this number 
is unlikely to be observed by chance if the categories are equally likely. On the other hand,
the second most frequent category (Oxide Defect) contains 25% of the defects, which is not too much larger than 16.67%.
Perhaps that 
category is no more likely than any of the other remaining categories? 
</p><p>
Wilkinson's chart visualizes the comparison between the observed percentages and the null hypothesis where every category is equally likely. 
For each category, the graph shows two features:
</p>
<ul>
<li>
The dots show the observed percentage of defects in each category. Thus, the first category (Contamination) shows a dot at 42.5%,
the second shows a dot at 25%, the third shows a dot at 12.5%, and so forth.
</li>
<li>
The bars represent 95% confidence intervals <em>under the null hypothesis</em> that all six causes are equally probable and under the assumption that the bars are being displayed in decreasing order of size.
Wilkinson calls these the <em>acceptance regions</em>.
If the observed percentage is outside and above the acceptance region (as it is for the Contamination category), it indicates that the 
most-frequent category is observed significantly more often than would be expected for the most-frequent category under the null scenario.
If the observed percentage is inside the acceptance region (for example, Oxide Defect, 
Metallization, and Silicon Defect), it indicates that the 
observed frequency is not different from what would be expected under the null scenario.
If the observed percentage is below the acceptance region (Corrosion and Doping),
the observed frequency is less than what would be expected under the null scenario.
</li>
</ul>

<p>
When I first saw this plot, I was a little confused because I expected the acceptance regions to be centered around 16.7%, 
which is the expected value for six equally probable categories. However, they are not!
The reason for this discrepancy is that the acceptance regions represent the 
range of 95% of the <em>ranked</em> categories under the hypothesis that all categories are equally probable.
</p><p>
If all categories were equally probable, random variation 
causes some categories to have more observations than others. 
When you sort the categories, the first-ranked bar is almost always taller than 16.7%.
Similarly, the last-ranked bar is almost always less than 16.7%. Thus, you must adjust the heights of the 
acceptance regions to account for the fact that the categories are shown in decreasing order.
</p>

<h3>Using simulation to create the acceptance intervals</h3>
<p>
You can use simulation to create the acceptance intervals, which are the ranges of the 
first-ranked, second-ranked, and third-ranked (etc.) categories when the 
categories are selected uniformly at random from a set of size N. In this example, N=40 
and there are K=6 categories. 
You can use the RANDMULTINOMIAL function in SAS IML software to simulate a large number (B=5000) of samples.
For each sample, sort the counts in descending order to mimic the method used by the Pareto chart.
You can do these two steps efficiently by simulating all B samples into a matrix that has B rows and K columns.
You then <a href="https://blogs.sas.com/content/iml/2026/06/24/sort-rows-cols-matrix.html">sort each row independently</a>.
</p><p>
After this procedure, the first column of the sorted matrix contains the distribution of counts for the first-ranked category
in each sample.
In general, the i_th column contains the distribution of counts for the i_th-ranked category.
You can therefore compute the 2.5th and 97.5 percentiles to form a 95% confidence interval for the counts.
This procedure is implemented in the following IML program:
</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: #0000ff;">call</span> randseed<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;">40</span>;                <span style="color: #006400; font-style: italic;">/* Total number of observations */</span>
K = <span style="color: #2e8b57; font-weight: bold;">6</span>;                 <span style="color: #006400; font-style: italic;">/* Number of categories */</span>
alpha = <span style="color: #2e8b57; font-weight: bold;">0.05</span>;          <span style="color: #006400; font-style: italic;">/* Significance level */</span>
B = <span style="color: #2e8b57; font-weight: bold;">5000</span>;              <span style="color: #006400; font-style: italic;">/* Number of Monte Carlo iterations */</span>
&nbsp;
<span style="color: #006400; font-style: italic;">/* Monte Carlo simulation from the Null distribution, which assumes uniform probability */</span>
prob = j<span style="color: #66cc66;">&#40;</span>K, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>/K<span style="color: #66cc66;">&#41;</span>;
Y = RandMultinomial<span style="color: #66cc66;">&#40;</span>B, <span style="color: #0000ff;">N</span>, prob<span style="color: #66cc66;">&#41;</span>;
<span style="color: #006400; font-style: italic;">/* Sort each row. Columns contain frequencies for each rank (1st, 2nd, 3rd,...).
   You could also use the SortMat function; see 
   https://blogs.sas.com/content/iml/2026/06/24/sort-rows-cols-matrix.html
*/</span>
<span style="color: #0000ff;">do</span> i = <span style="color: #2e8b57; font-weight: bold;">1</span> to nrow<span style="color: #66cc66;">&#40;</span>Y<span style="color: #66cc66;">&#41;</span>;
   v = colvec<span style="color: #66cc66;">&#40;</span>Y<span style="color: #66cc66;">&#91;</span>i,<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">call</span> sort<span style="color: #66cc66;">&#40;</span>v, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* sort descending */</span>
   Y<span style="color: #66cc66;">&#91;</span>i, <span style="color: #66cc66;">&#93;</span> = rowvec<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">end</span>;
w = <span style="color: #66cc66;">&#40;</span>alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span> // <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #0000ff;">call</span> qntl<span style="color: #66cc66;">&#40;</span>CL, Y, w<span style="color: #66cc66;">&#41;</span>;              <span style="color: #006400; font-style: italic;">/* 95% CL of counts for ranked categories */</span>
<span style="color: #006400; font-style: italic;">/* transpose CL to make it easier to visualize */</span>
CL = CL`;
print CL<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'LowerCL'</span> <span style="color: #a020f0;">'UpperCL'</span><span style="color: #66cc66;">&#125;</span> r=<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'R1'</span>:<span style="color: #a020f0;">'R6'</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#93;</span>;
<span style="color: #000080; font-weight: bold;">QUIT</span>;</pre></td></tr></table></div>





<img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt2.png" alt="" width="176" height="231" class="alignnone size-full wp-image-59297" />

<p>
The output shows the confidence intervals for counts when a set of K=6 categories are chosen uniformly at random (with replacement) N=40 times.
The first-ranked category (the most-frequent one) usually has 8-13 counts.
This is larger than the 40/6 = 6.67 counts that you might have expected.
Similarly, the second-ranked category usually has between 7-10 counts. 
The last two categories usually have 6 or fewer counts. 
Obviously, you can divide these counts by N=40 to get the corresponding percentages.
You can then write the percentages to a SAS data set and use a HIGHLOW statement in PROC SGPLOT to reproduce Wilkinson's Pareto chart.
</p>

<h3>A SAS macro to create acceptance regions for a Pareto chart</h3>
<p>
If you have a license for SAS IML software, the  following SAS macro computes the 
same alternative Pareto chart as the CHARTTYPE=INTERVALS option in PROC PARETO.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Plot the observed counts as a dot plot and overlay an acceptance band
   composed of the lower and upper 95% limits for the null distribution.
   This macro requires license for SAS IML.
   EXAMPLES:
   %AcceptancePareto(sashelp.cars, Type);   * 95% acceptance regions;
   %AcceptancePareto(sashelp.cars, Cylinders, alpha=0.1);   * 90% acceptance regions;
*/</span>
<span style="color: #0000ff;">%macro</span> AcceptancePareto<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">DSName</span>, <span style="color: #0000ff;">VarName</span>, alpha=<span style="color: #2e8b57; font-weight: bold;">0.05</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">%local</span> conf;
   <span style="color: #0000ff;">%let</span> conf = <span style="color: #0000ff;">%sysevalf</span><span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">100</span><span style="color: #006400; font-style: italic;">*(1-&amp;alpha));</span>
&nbsp;
   <span style="color: #000080; font-weight: bold;">proc freq</span> <span style="color: #000080; font-weight: bold;">data</span>=&amp;<span style="color: #0000ff;">DSName</span> <span style="color: #0000ff;">order</span>=Freq noprint;
      <span style="color: #0000ff;">where</span> <span style="color: #0000ff;">not</span> <span style="color: #0000ff;">missing</span><span style="color: #66cc66;">&#40;</span>&amp;<span style="color: #0000ff;">VarName</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">table</span> &amp;<span style="color: #0000ff;">VarName</span> / out=_FreqOut outcum;
   <span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* compute upper and lower CL */</span>
   <span style="color: #000080; font-weight: bold;">proc iml</span>;
   <span style="color: #0000ff;">call</span> randseed<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1234</span><span style="color: #66cc66;">&#41;</span>;
   use _FreqOut;   read all <span style="color: #0000ff;">var</span> <span style="color: #a020f0;">&quot;count&quot;</span>;   <span style="color: #0000ff;">close</span>;
&nbsp;
   <span style="color: #0000ff;">N</span> = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#40;</span>count<span style="color: #66cc66;">&#41;</span>;                <span style="color: #006400; font-style: italic;">/* Total number of observations */</span>
   K = nrow<span style="color: #66cc66;">&#40;</span>count<span style="color: #66cc66;">&#41;</span>;               <span style="color: #006400; font-style: italic;">/* Number of categories */</span>
   alpha = <span style="color: #0000ff; font-weight: bold;">&amp;alpha</span>;                <span style="color: #006400; font-style: italic;">/* Significance level */</span>
   B = <span style="color: #2e8b57; font-weight: bold;">5000</span>;                      <span style="color: #006400; font-style: italic;">/* Number of Monte Carlo iterations */</span>
&nbsp;
   <span style="color: #006400; font-style: italic;">/* Monte Carlo simulation from the Null distribution, which assumes uniform probability */</span>
   prob = j<span style="color: #66cc66;">&#40;</span>K, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span>/K<span style="color: #66cc66;">&#41;</span>;  <span style="color: #006400; font-style: italic;">/* H0: All categories are equally probable */</span>
   Y = RandMultinomial<span style="color: #66cc66;">&#40;</span>B, <span style="color: #0000ff;">N</span>, prob<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #006400; font-style: italic;">/* Sort each row. Columns contain frequencies for each rank (1st, 2nd, 3rd,...) */</span>
   <span style="color: #0000ff;">do</span> i = <span style="color: #2e8b57; font-weight: bold;">1</span> to nrow<span style="color: #66cc66;">&#40;</span>Y<span style="color: #66cc66;">&#41;</span>;
      v = colvec<span style="color: #66cc66;">&#40;</span>Y<span style="color: #66cc66;">&#91;</span>i,<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
      <span style="color: #0000ff;">call</span> sort<span style="color: #66cc66;">&#40;</span>v, <span style="color: #2e8b57; font-weight: bold;">1</span>, <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* sort descending */</span>
      Y<span style="color: #66cc66;">&#91;</span>i, <span style="color: #66cc66;">&#93;</span> = rowvec<span style="color: #66cc66;">&#40;</span>v<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">end</span>;
   w = <span style="color: #66cc66;">&#40;</span>alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span> // <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">call</span> qntl<span style="color: #66cc66;">&#40;</span>CL, Y, w<span style="color: #66cc66;">&#41;</span>;              <span style="color: #006400; font-style: italic;">/* 95% CL of counts for ranked categories */</span>
   <span style="color: #006400; font-style: italic;">/* transpose CL and convert to percentage */</span>
   CL = <span style="color: #2e8b57; font-weight: bold;">100</span> <span style="color: #006400; font-style: italic;">* CL` / N;</span>
   <span style="color: #0000ff;">create</span> _CL <span style="color: #0000ff;">from</span> CL<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'_Lower'</span> <span style="color: #a020f0;">'_Upper'</span><span style="color: #66cc66;">&#125;</span><span style="color: #66cc66;">&#93;</span>;
      append <span style="color: #0000ff;">from</span> CL;
   <span style="color: #0000ff;">close</span>;
   <span style="color: #000080; font-weight: bold;">QUIT</span>;
&nbsp;
   <span style="color: #000080; font-weight: bold;">data</span> _AcceptancePareto;
   <span style="color: #0000ff;">merge</span> _FreqOut _CL; 
   <span style="color: #0000ff;">label</span> _Lower = <span style="color: #a020f0;">&quot;Lower &amp;conf.% Acceptance Percent&quot;</span>
         _Upper = <span style="color: #a020f0;">&quot;Upper &amp;conf.% Acceptance Percent&quot;</span>
         Percent = <span style="color: #a020f0;">&quot;Observed Percent&quot;</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>=_AcceptancePareto;
      highlow <span style="color: #0000ff;">x</span>=&amp;<span style="color: #0000ff;">VarName</span> low=_Lower high=_Upper / 
              type=bar barwidth=<span style="color: #2e8b57; font-weight: bold;">0.8</span> legendlabel=<span style="color: #a020f0;">&quot;&amp;conf.% Acceptance Interval&quot;</span>;
      scatter <span style="color: #0000ff;">x</span>=&amp;<span style="color: #0000ff;">VarName</span> y=Percent;
      yaxis  <span style="color: #0000ff;">min</span>=<span style="color: #2e8b57; font-weight: bold;">0</span> offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span> grid <span style="color: #0000ff;">label</span>=<span style="color: #a020f0;">&quot;Percent&quot;</span>;
      xaxis type=discrete discreteorder=<span style="color: #000080; font-weight: bold;">data</span>;
      keylegend / location=Inside position=NE opaque across=<span style="color: #2e8b57; font-weight: bold;">1</span>;
      <span style="color: #0000ff;">format</span> Percent best4.;
   <span style="color: #000080; font-weight: bold;">run</span>;
<span style="color: #0000ff;">%mend</span>;
&nbsp;
<span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Revised Pareto Chart with 95% Acceptance Bands&quot;</span>;
title2 <span style="color: #a020f0;">&quot;H0: Uniform Probability for Causes&quot;</span>;
<span style="color: #0000ff;">footnote</span> J=L <span style="color: #a020f0;">&quot;Wilkinson (2006, TAS)&quot;</span>;
%AcceptancePareto<span style="color: #66cc66;">&#40;</span>Failure, Cause<span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt3.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59300" srcset="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt3.png 640w, https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
I've added grid lines and a legend, but the information in this plot is the same as the one produced by PROC PARETO.
The graph shows that the empirical probability of the Contamination defects is significantly different from what would be expected
if the defect causes were equally probable. Thus, the quality engineer should strive to reduce the contamination defects.
The observed count for Oxide Defect is not significantly higher than expected. It is on the boundary of the acceptance region, 
so the engineer might want to gather more data before dedicating resources to address the Oxide Defect.
</p>

<h3>Summary</h3>
<p>
This article shows how to create an alternative to the standard Pareto charts (Wilkinson, 2006).
The alternative chart provides <em>acceptance intervals</em> that indicate 
whether each category appears more often than would be expected if all categories are equally probable. 
This enables you to assess whether the tallest bars are "sufficiently tall" and are, in fact, occurring more often 
than chance. If not, 
there is little reason to spend time and money trying to address that cause.
</p><p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/07/06/alt-pareto-chart.html">An alternative Pareto chart 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/07/06/alt-pareto-chart.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/06/ParetoAlt3-150x150.png" />
	</item>
		<item>
		<title>Confidence limits for counts in a multinomial distribution</title>
		<link>https://blogs.sas.com/content/iml/2026/06/29/confidence-limits-multinomial.html</link>
					<comments>https://blogs.sas.com/content/iml/2026/06/29/confidence-limits-multinomial.html#respond</comments>
		
		<dc:creator><![CDATA[Rick Wicklin]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 09:23:49 +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=59246</guid>

					<description><![CDATA[<p>A recent article discussed Pareto charts and how to create them in SAS. A Pareto chart is used in quality control to identify the most likely reasons that a manufacturing process produces defective items. Suppose there are k different reasons why a process can fail, and you intend to randomly [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/06/29/confidence-limits-multinomial.html">Confidence limits for counts in a multinomial distribution</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 recent article discussed Pareto charts and how to create them in SAS.
A Pareto chart is used in quality control to identify the most likely reasons that 
a manufacturing process produces defective items.
Suppose there are <em>k</em> different reasons why a process can fail, and you intend to 
randomly select <em>N</em> defective items to examine.
The number of defective items for each reason can be modeled by using a <a href="https://blogs.sas.com/content/iml/2015/10/02/balls-and-urns2.html">multinomial distribution</a>.
</p><p>
A variation of the Pareto chart (<a href="https://www.tandfonline.com/doi/abs/10.1198/000313006X152243">Wilkinson, <em>TAS</em>, 2006</a>) uses
confidence intervals for multinomial counts
to enhance the information on the chart.
This article supplies the necessary background for 
how to estimate confidence intervals for multinomial counts. A subsequent article shows how to construct Wilkinson's Pareto chart in SAS.
</p>

<h3>The multinomial distribution</h3>
<p>
Suppose a categorical variable, X, can take on <em>k</em> different values.
A statistical model of the variable is to assume that X can be modeled by a multinomial distribution.
A multinomial distribution assumes that
the probability of the i_th value is <em>p<sub>i</sub></em>, where &Sigma;<sub>i</sub> <em>p<sub>i</sub></em> = 1.
</p><p>
In a sample of size <em>N</em>, there will be <em>N</em><sub>1</sub> items of the first type,
<em>N</em><sub>2</sub> items of the second type, and so forth, where &Sigma;<sub>i</sub> <em>N<sub>i</sub></em> = <em>N</em>. 
The <em>k</em>-tuple of counts
(<em>N</em><sub>1</sub>, <em>N</em><sub>2</sub>, ..., <em>N</em><sub><em>k</em></sub>) is a single observation from the multinomial distribution.  
</p><p>
The expected value of <em>N</em><sub><em>i</em></sub> is <em>N</em>*<em>p</em><sub><em>i</em></sub>,
but in an actual sample the count could be higher or lower than that value.
Confidence intervals enable you to answer the question, "what is the likely range of each count?"
</p><p>
A previous article describes <a href="https://blogs.sas.com/content/iml/2017/02/15/confidence-intervals-multinomial-proportions.html">how to construct simultaneous confidence intervals for the proportions</a>. But the Wilkinson variation of the Pareto chart
uses <em>marginal confidence limits</em>, which are different. 
Marginal confidence limits can be easily generated by running a simulation of the multinomial distribution and using percentiles of the results.
In SAS, the easiest way to simulate from the multinomial distribution is to use <a href="https://blogs.sas.com/content/iml/2013/08/05/simulate-from-multinomial-distribution.html">the RandMultinomial function in SAS IML</a>.
</p>


<h3>Simulating multinomial frequencies</h3>
<p>
Suppose a quality engineer samples 40 defective items. She finds that there are six causes for the defective items.
In the sample,  
<em>N</em><sub>1</sub>=17 items are defective because of the first cause, <em>N</em><sub>2</sub>=10 are defective because of the second cause, and the remaining causes 
are responsible for 5, 3, 3, and 2 counts, respectively.
The best estimate of the unknown probabilities is obtained by dividing the counts by <em>N</em>=40.
Thus, the empirical probabilities are 
p = {0.425, 0.25, 0.125, 0.075, 0.075, 0.05}.
</p><p>
The following call to PROC IML specifies these parameters and generates five random samples. Each sample is a random draw from the multinomial distribution.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* CI for marginal probability parameters in a multinomial distribution */</span>
<span style="color: #000080; font-weight: bold;">proc iml</span>;
<span style="color: #006400; font-style: italic;">/* observed frequencies */</span>
freq = <span style="color: #66cc66;">&#123;</span><span style="color: #2e8b57; font-weight: bold;">17</span>, <span style="color: #2e8b57; font-weight: bold;">10</span>, <span style="color: #2e8b57; font-weight: bold;">5</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;">2</span><span style="color: #66cc66;">&#125;</span>;
<span style="color: #0000ff;">N</span> = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#40;</span>freq<span style="color: #66cc66;">&#41;</span>;      <span style="color: #006400; font-style: italic;">/* sample size */</span>
p = freq / <span style="color: #0000ff;">N</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* 5 random samples of size N from Multinom(p) */</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>;
X5 = randmultinomial<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">5</span>, <span style="color: #0000ff;">N</span>, p<span style="color: #66cc66;">&#41;</span>;
print X5<span style="color: #66cc66;">&#91;</span>c=<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'C1'</span>:<span style="color: #a020f0;">'C6'</span><span style="color: #66cc66;">&#41;</span> r=<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'S1'</span>:<span style="color: #a020f0;">'S5'</span><span style="color: #66cc66;">&#41;</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/06/multinomialCL1.png" alt="" width="251" height="250" class="alignnone size-full wp-image-59264" srcset="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL1.png 251w, https://blogs.sas.com/content/iml/files/2026/06/multinomialCL1-150x150.png 150w" sizes="(max-width: 251px) 100vw, 251px" />

<p>
Each row shows the count in a random sample.
The i_th column shows the counts for the i_th defective cause across all five samples.
In this small simulation, the counts for the first cause range from 11-18, 
the counts for the second cause range from 7-11, and so forth.
Because the probabilities for the 4th-6th causes are relatively small, some samples did not contain any items
for those causes.
</p><p>
The small output gives us the intuition behind confidence intervals.
We'd like to know ranges that cover the counts for 95% of the samples.
You can do this by generating a large number of samples and calculating percentiles.
</p>


<h3>Confidence intervals for the multinomial counts</h3>
<p>
The previous output shows the range of counts for each variable for five random samples.
Now imagine generating many more random samples from the same multinomial distribution.
If you compute the 2.5th and 97.5th percentiles for each column, you obtain 
a simulation-based estimate of a 95% confidence interval for each column's values.
The following IML statements perform these calculations for a simulation that uses 10,000 random draws.
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Use simulation to estimate (1-alpha)*100% confidence intervals 
   for counts for each category.
   - N = sample size
   - p = parameter vector for Multinomial(N, p) distribution
   - B is the number of Monte Carlo simulations
   - alpha = significance level (default 0.05) ==&gt; 95% CL   
*/</span>
start MultinomialCL<span style="color: #66cc66;">&#40;</span>B, <span style="color: #0000ff;">N</span>, p, alpha=<span style="color: #2e8b57; font-weight: bold;">0.05</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">X</span> = RandMultinomial<span style="color: #66cc66;">&#40;</span>B, <span style="color: #0000ff;">N</span>, p<span style="color: #66cc66;">&#41;</span>; <span style="color: #006400; font-style: italic;">/* generate B random samples */</span>
   <span style="color: #006400; font-style: italic;">/* Find the (alpha / 2) and (1 - alpha / 2) percentiles.
      For alpha = 0.05, this estimates the 2.5th and 97.5th percentiles. */</span>
   w = <span style="color: #66cc66;">&#40;</span>alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span> // <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-alpha/<span style="color: #2e8b57; font-weight: bold;">2</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">call</span> qntl<span style="color: #66cc66;">&#40;</span>CL, <span style="color: #0000ff;">X</span>, w<span style="color: #66cc66;">&#41;</span>;       <span style="color: #006400; font-style: italic;">/* QNTL computes the quantiles of each column */</span>
   <span style="color: #0000ff;">return</span><span style="color: #66cc66;">&#40;</span> CL <span style="color: #66cc66;">&#41;</span>;
finish;
&nbsp;
<span style="color: #006400; font-style: italic;">/* call function and print the results in a table */</span>
limits = MultinomialCL<span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">10000</span>, <span style="color: #0000ff;">N</span>, p<span style="color: #66cc66;">&#41;</span>;
Category = <span style="color: #a020f0;">'C1'</span>:<span style="color: #a020f0;">'C6'</span>;
print limits<span style="color: #66cc66;">&#91;</span>r=<span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'Lower95'</span> <span style="color: #a020f0;">'Upper95'</span><span style="color: #66cc66;">&#125;</span> c=Category L=<span style="color: #a020f0;">&quot;Marginal Limits (N=40)&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/06/multinomialCL2.png" alt="" width="299" height="148" class="alignnone size-full wp-image-59261" srcset="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL2.png 299w, https://blogs.sas.com/content/iml/files/2026/06/multinomialCL2-164x82.png 164w" sizes="(max-width: 299px) 100vw, 299px" />

<p>
The table shows 95% confidence limits 
for the counts from a multinomial sample of size <em>N</em>=40 that has a vector of probability parameters <em>p</em>.
For the most probable category (C1), you can expect the count to be between 11 and 23 for 95% of samples.
For the least probable category (C6), you can expect the count to be between 0 and 5.
</p>
<p>
Although the table provides all relevant information, I like to graph the confidence intervals as 
bars in a chart that graphs the observed frequency and confidence intervals versus the categories, 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;">/* graph the empirical frequencies and the marginal CLs */</span>
Lower95 = limits<span style="color: #66cc66;">&#91;</span><span style="color: #2e8b57; font-weight: bold;">1</span>,<span style="color: #66cc66;">&#93;</span>;
Upper95 = limits<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;">create</span> MultinomialLimits <span style="color: #0000ff;">var</span> <span style="color: #66cc66;">&#123;</span><span style="color: #a020f0;">'Category'</span> <span style="color: #a020f0;">'Freq'</span> <span style="color: #a020f0;">'Lower95'</span> <span style="color: #a020f0;">'Upper95'</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: #0000ff;">title</span> <span style="color: #a020f0;">&quot;95% Confidence Limits for Frequencies&quot;</span>;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=MultinomialLimits noautolegend;
   highlow <span style="color: #0000ff;">x</span>=Category low=Lower95 high=Upper95 / 
           type=bar barwidth=<span style="color: #2e8b57; font-weight: bold;">0.8</span>;<span style="color: #006400; font-style: italic;">* primary=true;</span>
   scatter <span style="color: #0000ff;">x</span>=Category y=Freq;
   yaxis offsetmin=<span style="color: #2e8b57; font-weight: bold;">0</span> grid;
   xaxis type=discrete discreteorder=<span style="color: #000080; font-weight: bold;">data</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>





<a href="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL3.png" alt="" width="480" height="360" class="alignnone size-full wp-image-59267" srcset="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL3.png 640w, https://blogs.sas.com/content/iml/files/2026/06/multinomialCL3-300x225.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></a>

<p>
The graph enables you to see at a glance the ranges of counts for a sample of size <em>N</em> from the multinomial(N, p) distribution.  
</p>

<h3>Summary</h3>
<p>
The statistics of small random samples can be highly variable. This article looks at the counts in a multinomial distribution.
By running a simple simulation study, you can 
construct 95% confidence intervals for the counts. This is helpful for quality engineers who need to know whether an increase or decrease in an observed count is associated with a change in the manufacturing process or is merely random variation.
These confidence intervals are used in constructing Wilkinson's variation of the Pareto chart, which I will construct in a future article.
</p>

<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/iml/2026/06/29/confidence-limits-multinomial.html">Confidence limits for counts in a multinomial distribution</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/06/29/confidence-limits-multinomial.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/iml/files/2026/06/multinomialCL3-150x150.png" />
	</item>
	</channel>
</rss>
