В математической статистике находят применение три вида средних значений: среднее арифметическое, медиана и мода.
Среднее арифметическое вычисляется тривиально – находим сумму, пользуясь рецептом 3.6, и делим ее на количество слагаемых.
Медиана – это число, которое оказывается в середине набора после его сортировки. Если количество членов набора четно, то берется среднее двух чисел, оказавшихся в середине.
1 2 3 4 5 6 7 8 9101112131415161718192021
<xsl:templatename="ckbk:median"><xsl:paramname="nodes"select="/.."/><xsl:variablename="count"select="count($nodes)"/><xsl:variablename="middle"select="ceiling($count div 2)"/><xsl:variablename="even"select="not($count mod 2)"/><xsl:variablename="m1"><xsl:for-eachselect="$nodes"><xsl:sortdata-type="number"/><xsl:iftest="position() = $middle"><xsl:value-ofselect=". + ($even * ./following-sibling::*[1])"/></xsl:if></xsl:for-each></xsl:variable><!-- Медиана --><xsl:value-ofselect="$m1 div ($even + 1)"/></xsl:template>
Случай четного количества узлов обрабатывается с помощью уже встречавшегося нам трюка с преобразованием булевской величины в число. Если количество узлов нечетно, то $m1 в конце концов оказывается средним узлом, и для получения ответа мы делим его на 1. Если же количество узлов четно, то на последнем шаге $m1 будет равно сумме двух средних узлов, и эта величина делится на 2.
Мода – это элемент (или элементы), наиболее часто встречающийся в множестве, которое не обязательно состоит из чисел. Если одинаковость узлов можно установить сравнением их строковых значений, то годится следующее решение:
<xsl:templatename="ckbk:mode"><xsl:paramname="nodes"select="/.."/><xsl:paramname="max"select="0"/><xsl:paramname="mode"select="/.."/><xsl:choose><xsl:whentest="not($nodes)"><xsl:copy-ofselect="$mode"/></xsl:when><xsl:otherwise><xsl:variablename="first"select="$nodes[1]"/><xsl:variablename="try"select="$nodes[. = $first]"/><xsl:variablename="count"select="count($try)"/><!-- Рекурсия по узлам, не равным первому --><xsl:call-templatename="ckbk:mode"><xsl:with-paramname="nodes"select="$nodes[not(. = $first)]"/><!-- Если мы нашли узел, встречающийся чащедругих, передаем count, иначе прежнее значениесчетчика максимального числа вхождений --><xsl:with-paramname="max"select="($count > $max) * $count + not($count > $max) * $max"/><!-- Вычисляем новую моду ... --><xsl:with-paramname="mode"><xsl:choose><!-- первый элемент в try, если найден новый максимум --><xsl:whentest="$count > $max"><xsl:copy-ofselect="$try[1]"/></xsl:when><!-- старая мода объединяется с первым элементом в try, есличисло вхождений равно текущему максимуму --><xsl:whentest="$count = $max"><!-- Caution: you will need to convert $mode to a --><!-- node set if you are using a version of XSLT --><!-- that does not convert automatically --><xsl:copy-ofselect="$mode | $try[1]"/></xsl:when><!-- в противном случае старая мода не изменяется --><xsl:otherwise><xsl:copy-ofselect="$mode"/></xsl:otherwise></xsl:choose></xsl:with-param></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
Если это не так, заменим сравнение подходящей проверкой. Например, если эквивалентность означает равенство атрибута age, то проверка выглядит так: ./@age = $first/age. Дисперсия и стандартное отклонение применяются в статистике для оценки разброса значений относительно среднего. Простейший способ вычисления дисперсии основан на вычислении трех величин: sum – сумма всех чисел, sum-sq – сумма квадратов чисел и count – количество чисел. Тогда дисперсия равна (sum-sq - sum2 / count) / count - 1. Все это можно вычислить в одном шаблоне с хвостовой рекурсией:
Вероятно, вы заметили, что этот шаблон – вариация на тему ckbk:sum, мы просто включили в него вычисление еще двух компонентов дисперсии. Поэтому, если процессор XSLT не поддерживает хвостовую рекурсию, то на больших наборах данных неизбежны проблемы. В таком случае придется обратиться к другой стратегии, основанной на общепринятом определении дисперсии: ?(mean - xi)2 / (count - 1). Сначала методом «разделяй и властвуй» или разбиения на части мы вычисляем сумму элементов, и делением на count получаем среднее. Затем таким же образом вычисляется сумма квадратов разностей между каждым элементом и средним. И, наконец, делим результат на count - 1.
Зная дисперсию, вычислить стандартное отклонение тривиально – нужно лишь извлечь квадратный корень. Об извлечении корня см. рецепт 3.5.
<!-- Медиана --><xsl:functionname="ckbk:median"><xsl:paramname="nodes"as="xs:double*"/><xsl:variablename="count"select="count($nodes)"/><xsl:variablename="middle"select="ceiling($count div 2)"/><xsl:variablename="sorted"as="xs:double*"><xsl:perform-sortselect="$nodes"><xsl:sortdata-type="number"/></xsl:perform-sort></xsl:variable></xsl:function>
<!-- Мода --><xsl:functionname="ckbk:mode"as="item()*"><xsl:paramname="nodes"as="item()*"/><!-- Сначала на одим уникальны знач ни --><xsl:variablename="distinct"select="distinct-values($nodes)"as="item()*"/><!-- Получа м посл доват льность сч тчиков числа в о д ний ка до оуникально о знач ни --><xsl:variablename="counts"select="for $i in $distinct return count($nodes[. = $i])"as="xs:integer*"/><!-- На одим максимальный сч тчик --><xsl:variablename="max"select="max($counts)"as="xs:integer?"/><!-- Возвращаем значения, встречающиеся максимальное число раз --><xsl:sequenceselect="$distinct[position() = index-of($counts,$max)]"/></xsl:function>
Статистические функции повсеместно применяются для анализа числовых данных, так что приведенные выше шаблоны окажутся полезным добавлением в копилку ваших инструментов. Однако XSLT никогда не задумывался как язык для статистического анализа. Альтернативный подход состоит в том, чтобы воспользоваться XSLT для предварительного преобразования данных из формата XML в формат, где значения разделены запятыми или символами табуляции, с последующим импортом файла в электронную таблицу или специализированный пакет статистических программ.
Как и раньше, в версии XSLT 2.0 решение записывается значительно проще. Еще важнее тот факт, что реализация моды в этой версии гораздо быстрее и компактнее, да и отлаживать ее легче. Помнится, у меня ушло не меньше двух часов на то, чтобы написать правильный код для первого издания этой книги. А в версии 2.0 потребовалось всего-то 15 минут после того, как я понял, что можно воспользоваться функцией distinct-values.