Search code examples
xmlxsltxslt-2.0

XSL percentile function


Is there way how to get 90, 95 or 99 percentile from input values with XSLT 2.0 or 3.0? Below is a code from this question How to print percentile using xsl, but is is not working correctly. This gives me a avarage from two values and not one value which is that wanted percentil.

From this "online calculator" is result 5. https://www.emathhelp.net/calculators/probability-statistics/percentile-calculator/?i=2%2C4%2C3%2C1%2C5&p=90&steps=on

XML

<root>
    <value t="5"></value>
    <value t="1"></value>
    <value t="2"></value>
    <value t="4"></value>
    <value t="3"></value>
</root>

XSL

    <xsl:template match="/">
      <xsl:variable name="thisPercentile">
         <xsl:call-template name="percentiles">
            <xsl:with-param name="responsetimes" select="/root/*/@t" />
            <xsl:with-param name="percentile" select="0.9" />
         </xsl:call-template>
      </xsl:variable>

      <xsl:value-of select="$thisPercentile" />
    </xsl:template>



<xsl:template name="percentiles">
   <xsl:param name="responsetimes" select="." />
   <xsl:param name="percentile" select="." />
   <xsl:variable name="sortedresponsetimes">
        <xsl:for-each select="$responsetimes">
            <xsl:sort data-type="number" />
            <xsl:element name="time">
                <xsl:value-of select="." />
            </xsl:element>
        </xsl:for-each>
   </xsl:variable>
   <xsl:variable name="n" select="count($responsetimes)-1" />
   <xsl:variable name="k" select="floor($percentile*$n)+1" />
   <xsl:variable name="f" select="($percentile*$n+1)-$k" />
   <xsl:variable name="a0" select="$sortedresponsetimes[1]/time[$k]" />
   <xsl:variable name="a1" select="$sortedresponsetimes[1]/time[$k+1]" />
    <xsl:value-of select="$a0 + ( $f * ( $a1 - $a0))" />
</xsl:template>

Ouput after transform

4.6


Solution

  • In XPath 3.1 making use of the sort function you could implement that algorithm given on the linked page as

      <xsl:function name="mf:percentile" as="xs:decimal">
          <xsl:param name="input-sequence" as="xs:decimal*"/>
          <xsl:param name="p" as="xs:integer"/>
          <xsl:sequence select="let $sorted-input := sort($input-sequence),
                                $i := round($p div 100 * count($sorted-input))
                                return $sorted-input[$i]"/>
      </xsl:function>
    

    in a complete sample as

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="xs mf"
        version="3.0">
    
      <xsl:function name="mf:percentile" as="xs:decimal">
          <xsl:param name="input-sequence" as="xs:decimal*"/>
          <xsl:param name="p" as="xs:integer"/>
          <xsl:sequence select="let $sorted-input := sort($input-sequence),
                                $i := round($p div 100 * count($sorted-input))
                                return $sorted-input[$i]"/>
      </xsl:function>
    
      <xsl:output method="text"/>
    
      <xsl:template match="root">
          <xsl:value-of select="mf:percentile(value/@t, 90)"/>
      </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/bFukv8u

    In XSLT/XPath 2.0 you can use perform-sort and then local XSLT variables in the function:

      <xsl:function name="mf:percentile" as="xs:decimal">
          <xsl:param name="input-sequence" as="xs:decimal*"/>
          <xsl:param name="p" as="xs:integer"/>
          <xsl:variable name="sorted-input" as="xs:decimal*">
              <xsl:perform-sort select="$input-sequence">
                  <xsl:sort select="."/>
              </xsl:perform-sort>
          </xsl:variable>
          <xsl:variable name="i" select="round($p div 100 * count($sorted-input))"/>
          <xsl:sequence select="$sorted-input[$i]"/>
      </xsl:function>
    

    http://xsltransform.hikmatu.com/3Nqn5Yc