<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Excel Programming   : Functions</title><link>http://blogs.msdn.com/gabhan_berry/archive/tags/Functions/default.aspx</link><description>Tags: Functions</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Writing Custom Excel Worksheet Functions in C#</title><link>http://blogs.msdn.com/gabhan_berry/archive/2008/04/07/writing-custom-excel-worksheet-functions-in-c_2D00_sharp.aspx</link><pubDate>Mon, 07 Apr 2008 14:00:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8346368</guid><dc:creator>Gabhan Berry</dc:creator><slash:comments>16</slash:comments><comments>http://blogs.msdn.com/gabhan_berry/comments/8346368.aspx</comments><wfw:commentRss>http://blogs.msdn.com/gabhan_berry/commentrss.aspx?PostID=8346368</wfw:commentRss><description>&lt;P&gt;Writing our own, custom worksheet functions is a great way to extend Excel. Before Excel 2002 we developed custom functions by either writing them&amp;nbsp;using VBA inside an XLA&amp;nbsp;or&amp;nbsp;by using C/C++ inside an XLL. Excel 2002 introduced a new type of addin called an &lt;EM&gt;automation addin&lt;/EM&gt;. An automation addin enables&amp;nbsp;Excel to&amp;nbsp;call functions on COM objects&amp;nbsp;from cells on a worksheet. In other words,&amp;nbsp;it enabled us to call COM functions just as if they were&amp;nbsp;normal, built-in Excel functions.&lt;/P&gt;
&lt;P&gt;This opened up the world of custom worksheet functions to COM developers. So, now we can now write&amp;nbsp;custom functions in any COM language; including C#.&lt;/P&gt;
&lt;P&gt;In this article we'll&amp;nbsp;write a custom worksheet function in C# called UNIQUEVALUES.&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;STRONG&gt;What&amp;nbsp;will our&amp;nbsp;Custom Function Do?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;The UNIQUEVALUES function will return an array of the unique values in a specified range. For example, if the range A1:A5 contains the values Red, Yellow, Red, Blue, Yellow, then {=UNIQUEVALUES(A1:A5)} will return the array {Red, Yellow, Blue}. &lt;/P&gt;
&lt;P mce_keep="true"&gt;The function will also be able to cope with a mixture of numerical data and text data. So, if A1:A5 contains the values: Red, 1, 1, Red then&amp;nbsp;{=UNIQUEVALUES(A1:A5)} will return the array {Red, 1}.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Because UNIQUEVALUES is a worksheet function that references the source data, whenever the values in the source range change the function automatically extracts the new unique values from the range. So we know that the values returned from the function are always up-to-date (once the worksheet has calculated).&lt;/P&gt;
&lt;P mce_keep="true"&gt;Notice that the UNIQUEVALUES function returns an array. This means that we &lt;EM&gt;array enter&lt;/EM&gt; the funtion by pressing Ctrl-Shift-Enter rather than just Enter when we type the function into the worksheet. &lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;STRONG&gt;Coding the UNIQUEVALUES Function&lt;/STRONG&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;There are two tasks we need to do in order to make a C# class's methods be callable as worksheet functions: &lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;Define our worksheet functions&amp;nbsp;in&amp;nbsp;the way Excel expects;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;Add the Programmable key to the registry for our class's CLSID;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P mce_keep="true"&gt;The first&amp;nbsp;task seems intriguing: &lt;EM&gt;Define our worksheet functions&amp;nbsp;in&amp;nbsp;the way Excel expects&lt;/EM&gt;. What does that mean?&lt;/P&gt;
&lt;P mce_keep="true"&gt;Well, this is where we have to remember that Excel is speaking to our automation addin via COM. So&amp;nbsp;Excel expects to be able to use COM techniques&amp;nbsp;to discover and access our functions. Once we know what these COM techniques are, we can make sure that our C# class interoperates with COM in the appropriate way. &lt;/P&gt;
&lt;P mce_keep="true"&gt;When an automation addin is loaded, Excel needs to discover which functions that addin supports. For example, our automation addin&amp;nbsp;will have a UNIQUEVALUES function that we want to use in worksheets. How does Excel discover&amp;nbsp;that our addin&amp;nbsp;supports that function and how does it discover&amp;nbsp;information about the parameters of the function? &lt;/P&gt;
&lt;P mce_keep="true"&gt;Excel discovers this information using a COM interface called ITypeInfo. &lt;/P&gt;
&lt;P mce_keep="true"&gt;ITypeInfo&amp;nbsp;is&amp;nbsp;an interface that is&amp;nbsp;used to access metadata about a&amp;nbsp;COM type. It is similar &lt;EM&gt;in concept&lt;/EM&gt; to .NET reflection, but is very different in implemenation.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Excel uses ITypeInfo to discover the names and the parameter details of the functions&amp;nbsp;exposed by our automation addin. This is how Excel knows&amp;nbsp;to call our addin whenever it&amp;nbsp;comes across&amp;nbsp;a&amp;nbsp;call to UNIQUEVALUES.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;But Excel will only query the default interface of our COM class. So we need to make sure that the functions we want to expose as worksheet functions are defined on the default interface of our class. &lt;/P&gt;
&lt;P mce_keep="true"&gt;There are two ways of doing this in C#:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;By specifying the &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual&lt;/SPAN&gt; attribute on our C# class;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;By defining our worksheet functions on a dedicated interface and using the &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ComDefaultInterface &lt;/SPAN&gt;attribute to make that interface the default interface of our class;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P mce_keep="true"&gt;To understand what the &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual &lt;/SPAN&gt;attribute value does to our COM interface, we need to understand a little more about COM interfaces.&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;In COM,&amp;nbsp;the IDispatch interface enables clients to dynamically call your functions at runtime. Instead of the compiler checking that functions exist and that the parameters types match at compile-time, IDispatch enables clients to do so&amp;nbsp;at runtime. This enables&amp;nbsp;components&amp;nbsp;to be extremely loosely coupled. This type of function call is termed: &lt;EM&gt;late-bound&lt;/EM&gt;. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Support of IDispatch is optional. COM classes do not &lt;EM&gt;have&lt;/EM&gt; to support it. However, by default, C# classes support IDispatch. In fact (unless we specify otherwise)&amp;nbsp;the default interface created for a C# class is a dispatch interface.&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;Why do we need to care about this? Well, since the default interface is a dispatch interface&amp;nbsp;our worksheet functions are not &lt;EM&gt;explicitly&lt;/EM&gt; defined on the interface and are therefore not discoverable by Excel.&amp;nbsp;So, even though our C# class may have a public function named UNIQUEVALUES Excel would not be able to call it.&lt;/P&gt;
&lt;P mce_keep="true"&gt;A &lt;EM&gt;dual&lt;/EM&gt; interface allows a dispatch interface to explicitly define custom functions in addition to those already defined&amp;nbsp;by IDispatch.&amp;nbsp;The &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual &lt;/SPAN&gt;attribute turns our COM interface into a dual interface. This effectively takes all public functions defined on our class&amp;nbsp;(including those inherited from base classes) and explicitly defines them on our COM interface which in turn&amp;nbsp;makes them&amp;nbsp;discoverable&amp;nbsp;by Excel.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Thus, an easy way of getting our C# class to define Excel worksheet functions is to simply mark the class with&amp;nbsp;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual. &lt;/SPAN&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;However, there is a downside to doing this. All public functions on our class are then available as worksheet functions, including those inherited from System.Object. This isn't ideal. It would be better if we could have more control over what gets exposed to Excel and make sure that only the functions that are intended to be worksheet functions are actually discoverable by Excel. This is the reason I prefer not to use the &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual &lt;/SPAN&gt;attribute.&lt;/P&gt;
&lt;P mce_keep="true"&gt;(There is also a problem with COM versioning when using &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ClassInterfaceType&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;.AutoDual &lt;/SPAN&gt;- but we won't cover that here).&lt;/P&gt;
&lt;P mce_keep="true"&gt;Instead, I prefer to define the worksheet functions on a dedicated interface and have my C# class implement that interface. &lt;/P&gt;
&lt;P mce_keep="true"&gt;So, let's define our worksheet function interface. We'll call it &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IFunctions&lt;/SPAN&gt;&lt;/FONT&gt;. &lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;public&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;interface&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IFunctions&lt;/SPAN&gt; {&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;&lt;FONT color=#000000&gt;&amp;nbsp; &lt;/FONT&gt;object&lt;/SPAN&gt;[,] UNIQUEVALUES(Excel.&lt;SPAN style="COLOR: #2b91af"&gt;Range&lt;/SPAN&gt; TargetRange);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;/SPAN&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;Notice that the UNIQUEVALUES function returns a 2-d array of objects. This array will contain the unique values extracted from the specified &lt;FONT face="Courier New"&gt;Excel.&lt;SPAN style="COLOR: #2b91af"&gt;Range&lt;/SPAN&gt;&lt;/FONT&gt; object. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Next, we define our functions class and have it implement the &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IFunctions &lt;/SPAN&gt;&lt;/FONT&gt;interface. We also use the default attribute to specify that we want the IFunctions interface to be the class's default COM interface. &lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;[&lt;SPAN style="COLOR: #2b91af"&gt;ComDefaultInterface&lt;/SPAN&gt;(&lt;SPAN style="COLOR: blue"&gt;typeof&lt;/SPAN&gt;(&lt;SPAN style="COLOR: #2b91af"&gt;IFunctions&lt;/SPAN&gt;))]&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;class&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;Functions&lt;/SPAN&gt; : &lt;SPAN style="COLOR: #2b91af"&gt;IFunctions&lt;/SPAN&gt; {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;&amp;nbsp; public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[,] UNIQUEVALUES(Excel.&lt;SPAN style="COLOR: #2b91af"&gt;Range&lt;/SPAN&gt; TargetRange) {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; object&lt;/SPAN&gt;[,] values = TargetRange.get_Value(System.Reflection.&lt;SPAN style="COLOR: #2b91af"&gt;Missing&lt;/SPAN&gt;.Value) &lt;SPAN style="COLOR: blue"&gt;as&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[,];&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;List&lt;/SPAN&gt;&amp;lt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;&amp;gt; unqVals = &lt;SPAN style="COLOR: blue"&gt;new&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;List&lt;/SPAN&gt;&amp;lt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;&amp;gt;();&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;foreach&lt;/SPAN&gt; (&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt; obj &lt;SPAN style="COLOR: blue"&gt;in&lt;/SPAN&gt; values) {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;if&lt;/SPAN&gt; (!unqVals.Contains(obj))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;unqVals.Add(obj);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[,] resVals = &lt;SPAN style="COLOR: blue"&gt;new&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[unqVals.Count, 1];&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;for&lt;/SPAN&gt; (&lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt; idx = 0; idx &amp;lt; resVals.Length; ++idx)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;resVals[idx, 0] = unqVals[idx];&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;return&lt;/SPAN&gt; resVals;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;/SPAN&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;
&lt;P mce_keep="true"&gt;The algorithm for extracting the unique values is as follows (I confess that this may not be the optimal algorithm). &lt;/P&gt;
&lt;P mce_keep="true"&gt;We extract the values in the &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;Range&lt;/SPAN&gt; &lt;/FONT&gt;object into a 2-d array of &lt;FONT face="Courier New" color=#0000ff&gt;object&lt;/FONT&gt;. Because the array contains &lt;FONT face="Courier New" color=#0000ff&gt;object&lt;/FONT&gt;, we can cope with both textual data and numerical data. Next, we iterate over the array and store each value that hasn't already occurred in a &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;List&lt;/SPAN&gt;&amp;lt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;&amp;gt;&lt;/FONT&gt;. We then allocate a new 2-d array of &lt;FONT face="Courier New" color=#0000ff&gt;object &lt;/FONT&gt;and copy the unique values from the &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;List&lt;/SPAN&gt;&amp;lt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;&amp;gt; &lt;/FONT&gt;into the new array. Finally, we return the new array to Excel.&lt;/P&gt;
&lt;P mce_keep="true"&gt;
&lt;P mce_keep="true"&gt;When we return an array from a UDF Excel handles serialising the array into the worksheet in the appropriate way (as long as the user array-entered the cell formula). This means that the dimensions of the array matter. The first dimension of the array is mapped to columns. So if we return an &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[3]&lt;/FONT&gt; then Excel interprets this as &lt;EM&gt;"three columns containing one row each"&lt;/EM&gt; and places the three values adjacent to each other (i.e., each one in a different column). If we return an &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: blue"&gt;object&lt;/SPAN&gt;[1,3]&lt;/FONT&gt;then Excel interprets this as &lt;EM&gt;"one column containing three rows"&lt;/EM&gt; and places each value &lt;EM&gt;beneath&lt;/EM&gt; each other (i.e., each one in a different row). &lt;/P&gt;
&lt;P mce_keep="true"&gt;So, it is very easy to return an array to Excel. All we have to do is set the correct array dimensions and let Excel handle copying the values from the to the appropriate cells in the worksheet. &lt;/P&gt;
&lt;P mce_keep="true"&gt;We'll talk more about how arrays&amp;nbsp;are used in Excel worksheet functions later in the article.&lt;/P&gt;
&lt;P mce_keep="true"&gt;If we stop coding here, we'll find that Excel still can't use our class. There is one final thing we need to do: &lt;EM&gt;set the class's Programmable key in the registry&lt;/EM&gt;.&lt;/P&gt;
&lt;P mce_keep="true"&gt;The Programmable key indicates that a COM class exposes type information for its default interface.&amp;nbsp;The key is specified&amp;nbsp;under&amp;nbsp;the COM class's HKEY_CLASSES_ROOT\CLSID\{Guid} key (where {Guid} is the guid of the COM class).&lt;/P&gt;
&lt;P mce_keep="true"&gt;There is a convenient way to set this registry key from C#. There&amp;nbsp;exist two attributes called &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ComRegisterFunctionAttribute &lt;/SPAN&gt;and &lt;SPAN style="FONT-SIZE: 10pt; COLOR: #2b91af; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ComUnregisterFunctionAttribute&lt;/SPAN&gt;. These attributes are used to mark which functions COM should call&amp;nbsp;during the COM registration/unregistration process. So all we need to do is write a function that inserts the Programmable key during registration and removes it during unregistration.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;[&lt;SPAN style="COLOR: #2b91af"&gt;ComRegisterFunctionAttribute&lt;/SPAN&gt;]&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;static&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;void&lt;/SPAN&gt; RegisterFunction(&lt;SPAN style="COLOR: #2b91af"&gt;Type&lt;/SPAN&gt; type) {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;&amp;nbsp; Registry&lt;/SPAN&gt;.ClassesRoot.CreateSubKey(GetSubKeyName(type));&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;[&lt;SPAN style="COLOR: #2b91af"&gt;ComUnregisterFunctionAttribute&lt;/SPAN&gt;]&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;static&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;void&lt;/SPAN&gt; UnregisterFunction(&lt;SPAN style="COLOR: #2b91af"&gt;Type&lt;/SPAN&gt; type) {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: #2b91af"&gt;&lt;FONT color=#000000&gt;&amp;nbsp; &lt;/FONT&gt;Registry&lt;/SPAN&gt;.ClassesRoot.DeleteSubKey(GetSubKeyName(type), &lt;SPAN style="COLOR: blue"&gt;false&lt;/SPAN&gt;);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;private&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;static&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;string&lt;/SPAN&gt; GetSubKeyName(&lt;SPAN style="COLOR: #2b91af"&gt;Type&lt;/SPAN&gt; type) {&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;string&lt;/SPAN&gt; s = &lt;SPAN style="COLOR: #a31515"&gt;@"CLSID\{"&lt;/SPAN&gt; + type.GUID.ToString().ToUpper() + &lt;SPAN style="COLOR: #a31515"&gt;@"}\Programmable"&lt;/SPAN&gt;;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;return&lt;/SPAN&gt; s;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;/SPAN&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;(Notice that the registration functions are &lt;FONT face="Courier New"&gt;&lt;SPAN style="COLOR: blue"&gt;static&lt;/SPAN&gt;&lt;/FONT&gt;).&lt;/P&gt;
&lt;P mce_keep="true"&gt;
&lt;P mce_keep="true"&gt;Now we've finished coding the &lt;SPAN style="COLOR: #2b91af"&gt;&lt;FONT face="Courier New"&gt;Functions&lt;/FONT&gt;&lt;/SPAN&gt; class and the UNIQUEVALUES function. We can now use from an Excel worksheet just like any other custom worksheet function.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;STRONG&gt;Using UNIQUEVALUES in a Worksheet&lt;/STRONG&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;The first to do is to install our &lt;SPAN style="COLOR: #2b91af"&gt;&lt;FONT face="Courier New"&gt;Functions&lt;/FONT&gt;&lt;/SPAN&gt; class as an automation addin. This enables Excel to call our custom UDFs. &lt;/P&gt;
&lt;P mce_keep="true"&gt;To install as an automation addin, we bring up the usual&amp;nbsp;Addin Manager via&amp;nbsp;Excel Options-&amp;gt;Add-Ins-&amp;gt;Manage Excel Add-ins. From this dialogue we click the Automation button and select ExcelExtensions.Functions from the list.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;IMG src="http://blogs.msdn.com/photos/gabhan_berry/images/8362070/original.aspx" mce_src="http://blogs.msdn.com/photos/gabhan_berry/images/8362070/original.aspx"&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;To use the function, we highlight the cells which we want the unique values written into, type the =UNIQUEVALUES( ... ) where ... is the range from which we want to extract the values and then press &lt;EM&gt;ctrl-shift-enter&lt;/EM&gt;. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Note that we must press ctrl-shift-enter to tell Excel that this function returns an array. Excel automatically serialises the values returned from the function into the array of highlighted cells. If the number of values returned from the function is less than the number of cells we highlighted, Excel places #N/A in each of the extra cells. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Consider the following example.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;IMG src="http://blogs.msdn.com/photos/gabhan_berry/images/8365876/original.aspx" mce_src="http://blogs.msdn.com/photos/gabhan_berry/images/8365876/original.aspx"&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;Notice that there are only four unique values in the range D3:D9. However, because we highlighted 7 cells (i.e., E3:E9) Excel has placed #N/A in each of the 3 additional cells.&lt;/P&gt;
&lt;P mce_keep="true"&gt;If we highlight too few cells (say, 2 cells) then Excel will place the first to values returned from the function in those cells.&lt;/P&gt;
&lt;P mce_keep="true"&gt;This is normal array function behaviour in Excel. Notice that we didn't have to write anything in our code to do this; Excel does this for us automatically. &lt;/P&gt;
&lt;P mce_keep="true"&gt;If the data in D3:D9 changes, Excel fires UNIQUEVALUES and our function recalculates the new unique values. &lt;/P&gt;
&lt;P mce_keep="true"&gt;In the following screenshot, we change the first value from Red to Yellow and the unique values automatically update:&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;IMG src="http://blogs.msdn.com/photos/gabhan_berry/images/8365918/original.aspx" mce_src="http://blogs.msdn.com/photos/gabhan_berry/images/8365918/original.aspx"&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;The UNIQUEVALUES function can also be used with Excel's native functions that work with arrays. Two examples of such&amp;nbsp;functions are COUNT and INDEX. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Both COUNT and INDEX can&amp;nbsp;accept arrays as parameters. So we can use these functions&amp;nbsp;on the array returned by UNIQUEVALUES.&lt;/P&gt;
&lt;P mce_keep="true"&gt;The formula =COUNT(UNIQUEVALUES(D3:D9)) returns the number of values in the array. The formula =INDEX(UNIQUEVALUES(D3:D9), 1, 1) returns the first value in the array; and =INDEX(UNIQUEVALUES(D3:D9), &lt;STRONG&gt;2&lt;/STRONG&gt;, 1) returns the second; and so on ... (remember that UNIQUEVALUES returns an array one column wide).&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;STRONG&gt;Summary&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;The functions that we want to expose as custom worksheet functions need to be defined on our class's default interface;&lt;/DIV&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;Defining our custom worksheet functions on a dedicated interface and using &lt;SPAN style="COLOR: #2b91af"&gt;&lt;FONT face="Courier New"&gt;ComDefaultInterface &lt;/FONT&gt;&lt;/SPAN&gt;to make that interface the default is one way of doing this;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;Our COM class must define the Programmable registry key under its CLSID key;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;We can return multiple values from a custom UDF by returning them in an array;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV mce_keep="true"&gt;Excel handles the serialisation of the array into the worksheet;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P mce_keep="true"&gt;&lt;STRONG&gt;Download&lt;/STRONG&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;I have added the UNIQUEVALUES function into my ExcelExtensions add-in. This can be downloaded from the MSDN Code Gallery by clicking &lt;A class="" href="http://code.msdn.microsoft.com/ExcelExtensions" mce_href="http://code.msdn.microsoft.com/ExcelExtensions"&gt;here&lt;/A&gt;.&lt;/P&gt;
&lt;P mce_keep="true"&gt;There are two downloads which can be downloaded from the Releases tab: ExcelExtensionsSrc.zip and ExcelExtensions.msi. &lt;/P&gt;
&lt;P mce_keep="true"&gt;ExcelExtensionsSrc.zip contains the complete source code and ExcelExtensions.msi is the set up program. &lt;/P&gt;
&lt;P mce_keep="true"&gt;Over time, I'll be adding additional feature to the addin.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8346368" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/Addins/default.aspx">Addins</category><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/.NET/default.aspx">.NET</category><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/Functions/default.aspx">Functions</category></item><item><title>Using Statistics in Business Intelligence</title><link>http://blogs.msdn.com/gabhan_berry/archive/2008/04/04/using-statistics-in-business-intelligence.aspx</link><pubDate>Fri, 04 Apr 2008 19:27:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8357483</guid><dc:creator>Gabhan Berry</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/gabhan_berry/comments/8357483.aspx</comments><wfw:commentRss>http://blogs.msdn.com/gabhan_berry/commentrss.aspx?PostID=8357483</wfw:commentRss><description>&lt;P&gt;One other area I work in is Business Intelligence. These days BI is an important technology to an increasing number of companies. There are many great BI tools out there, one of which is (of course)&amp;nbsp;Excel. &lt;/P&gt;
&lt;P&gt;One of the things that&amp;nbsp;makes Excel such a useful BI tool is its richness of functionality. It has so many existing features that can be used in the BI area - one of which is its statistical analysis functions.&lt;/P&gt;
&lt;P&gt;I posted an article over on the Excel team blog illustrating one way&amp;nbsp;we can use Excel's&amp;nbsp;statistical functions to extract useful information from business data. &lt;/P&gt;
&lt;P&gt;If you're interested in statistical analysis or BI in Excel, you may find the article interesting. Click &lt;A class="" href="http://blogs.msdn.com/excel/archive/2008/04/02/statistics-and-business-data-detecting-unexpected-values.aspx" mce_href="http://blogs.msdn.com/excel/archive/2008/04/02/statistics-and-business-data-detecting-unexpected-values.aspx"&gt;here&lt;/A&gt; to view the article.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8357483" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/Functions/default.aspx">Functions</category><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/BI/default.aspx">BI</category><category domain="http://blogs.msdn.com/gabhan_berry/archive/tags/Statistics/default.aspx">Statistics</category></item></channel></rss>