使用 C# 运行符号测试

若有需前后对比的数据,且要确定某种效果是否有统计依据,最常使用的是符号检验。通过举例可以很好地解释这个原理。


假设你在一家制药公司工作,想要确定一种新型减肥药是否有效。你找来八名志愿者服用这种减肥药长达几周的时间。观察八名实验对象在实验前后的体重变化。假设八名实验对象中有六名体重下降。有确凿的统计依据表明这种减肥药有效吗?


减肥药效是一个典型的符号检验示例,但此类检验也同样适用于许多 IT 和软件方案。假设你有 40 台 Web 服务器计算机,并应用了一个旨在提升性能的软件修补程序。测量应用此修补程序前后的响应时间。如果 32 台服务器的性能提升,2 台服务器的性能没有变化,6 台服务器的性能下降,能得出什么结论?


了解本文所述观点的最佳方式是查看图 1 中的演示程序。阅读完本文后,你将非常了解符号检验所能解决的问题类型、究竟该如何使用 C# 实现符号检验,以及该如何解读符号检验结果。本文展示了演示程序的所有源代码。还可以从本文随附的代码下载中获取完整的演示程序。


图 1:使用 C# 实现符号检验


演示程序设置了八对需前后对比的数据,旨在确定某减肥养生计划是否有效。从数据来看,六名实验对象的体重确实下降,而两名实验对象的体重上升。


演示程序计算的“无效”概率为 0.1445。如何解读此结果完全由你自己决定,例如,“数据表明减肥计划有效的迹象不充分 (p = 0.8555)。”


阅读本文的前提是,至少拥有中级编程技能,无需了解符号检验。演示代码用 C# 编写,依赖于 .NET System.Numerics 命名空间,因为需要使用 Microsoft .NET Framework 4(2010 年发布)或更高版本。


  演示程序结构  


为了创建演示程序,我启动了 Visual Studio,并从“新建项目”菜单项中选择了“C# 控制台应用程序模板”。我将此项目命名为“SignTestUsingCSharp”。在将模板代码加载到编辑器窗口之后,我右键单击了解决方案资源管理器窗口中的 Program.cs 文件,并将此文件重命名为“SignTestProgram.cs”,然后允许 Visual Studio 为我重命名 Program 类。


接下来,我右键单击了项目名称,然后选择了“添加 | 引用”项。在“组件 | 框架”列表中,我选择了 System.Numerics 命名空间,然后单击了“确定”,将其添加到我的项目。在编辑器窗口顶部,我删除了所有 using 语句(引用顶级 System 命名空间的语句除外),然后添加了 using 语句来引用 System.Numerics 命名空间。


图 2 展示了程序的整体结构。为简单起见,程序采用的是严格意义上的静态方法,而不是面向对象编程 (OOP) 方法。DoCounts 和 ShowVector 方法是实用工具帮助程序。计算无效概率的工作是由 BinomRightTail 方法完成。BinomProb 和 Choose 方法是 BinomRightTail 的帮助程序。


using System;using System.Numerics;namespace SignTestUsingCSharp
{class SignTestProgram{static void Main(string[] args){Console.WriteLine("\nBegin Sign Test demo \n");// All calling statements go here      Console.WriteLine("\n\nEnd Sign Test demo \n");Console.ReadLine();}static int[] DoCounts(double[] before,double[] after) { . . }static void ShowVector(string pre, double[] v,int dec, string post) { . . }static double BinomProb(int k, int n,double p) { . . }static double BinomRightTail(int k, int n,double p) { . . }static BigInteger Choose(int n, int k) { . . }}
}

图 2:符号检验演示程序结构


在显示几条介绍信息之后,Main 方法设置并显示符号检验的演示数据:


double[] before = new double[] { 70, 80, 75, 85, 70, 75, 50, 60 };double[] after  = new double[] { 65, 78, 72, 87, 68, 74, 48, 63 };
Console.WriteLine("The weight data is: \n");
ShowVector("Before:  ", before, 0, "");
ShowVector("After :  ", after, 0, "\n");


在数据对超过约 30 个的非演示方案中,你会将数据存储在文本文件中,并编写用于读取和存储数据的帮助程序方法。使用平行数组是实现符号检验时最常用的方法。


接下来,演示使用 DoCounts 方法计算项目对数。如果体重下降,视为“成功项”,如果体重上升,视为“失败项”:


int[] counts = DoCounts(before, after);
Console.WriteLine("Num success = " + counts[2]);
Console.WriteLine("Num failure = " + counts[0]);


返回值是一个数组,其中存储单元 0 保存失败项数(体重上升),存储单元 1 保存无变化项数,存储单元 2 保存成功项数(体重下降)。


在计算机还没有普及的年代,计数是手动完成的,即在成功项旁边添加“+”号,在失败项旁边添加“-”号。这就是符号检验的命名由来。对于演示数据,手动方法如下所示:


Before:  70 80 75 85 70 75 50 60
After :  65 78 72 87 68 74 48 63+  +  +  -  +  +  +  -


请注意,符号检验不会考虑体重上升或下降的幅度。接下来,演示准备调用符号检验,如下所示:


int k = counts[2];int n = counts[0] + counts[2];
Console.WriteLine("k = " + k + " n = " + n + " p = 0.5");


变量 k 保存成功项数。变量 n 保存数据对总数。在此示例中,体重前后都有变化。在这种情况下,最常用的方法是不考虑无变化项。然而,在某些情况下,可能需要将无变化项添加为成功项或失败项。


例如,在减肥程序中,体重无变化很有可能会被视为失败。

Main 方法生成的结果如下:


double p_value = BinomRightTail(k, n, 0.5);
Console.WriteLine("\nProbability of 'no effect' is " + p_value.ToString("F4"));
Console.WriteLine("Probability of 'an effect' is " + (1 - p_value).ToString("F4"));


符号检验实际上是更为普遍的二项分布检验的具体示例。程序定义的函数 BinomRightTail 接受成功项数、数据对数以及概率值(在此示例中,为 0.5)。当二项分布检验使用 0.5 作为概率参数时,就是符号检验,我很快就会对此进行介绍。


  了解 Choose 函数  


符号检验采用二项分布,此分布反过来使用 Choose 函数。Choose(n, k) 函数返回从 n 个项中选择 k 个项的方法数。例如,Choose(5, 3) 返回从五个项中选择三个项时可以采用的方法数。假设这五个项为 (A, B, C, D, E)。从中选择三个项的方法有 10 种:


(A, B, C), (A, B, D), (A, B, E), (A, C, D), (A, C, E),
(A, D, E), (B, C, D), (B, C, E), (B, D, E), (C, D, E)


Choose 函数的定义为 Choose(n, k) = n! / [k! * (n-k)!],其中“!”字符表示阶乘。那么,


Choose(5, 3) = 5! / (3! * 2!) = (5 * 4 * 3 * 2 * 1) / (3 * 2 * 1) *(2 * 1) = 120 / 12 = 10


实现 Choose 函数比较棘手,因为即使 n 和 k 值适中,返回值也可能会非常大。例如,


Choose(100, 25) = 242,519,269,720,337,121,015,504


为了能够返回非常大的值(在符号检验中可能会发生),演示程序在 System.Numerics 命名空间中使用 BigInteger 类型。Choose 的演示实现运用了两个数学技巧来提高效率。首先,事实证明,Choose(n, k) = Choose(n, n-k)。例如,


Choose(10, 7) = Choose(10, 3)


使用较小的 k 值,可以减少计算量。其次,Choose 还有另一种定义,以下示例最能解释:


Choose(10, 3) = (10 * 9 * 8) / (3 * 2 * 1)


也就是说,分母就是 k!,分子直接使用 n! 公式的前 k 项,很多项抵消掉了。综上,图 3 展示了 Choose 的演示实现。


static BigInteger Choose(int n, int k)
{if (n == k) return 1; // Required special case  int delta, iMax;if (k < n - k) { // Ex: Choose(100,3)    delta = n - k;iMax = k;}else { // Ex: Choose(100,97)    delta = k;iMax = n - k;}BigInteger ans = delta + 1;for (int i = 2; i <= iMax; ++i)ans = (ans * (delta + i)) / i;return ans;
}

图 3:Choose 函数


  了解二项分布  

了解如何实现和解读符号检验的关键是了解二项分布。通过示例最能对此进行解释。


假设你有一个有偏硬币设计,投掷硬币出现正面的概率是 0.6,出现反面的概率是 0.4,并将出现正面定义为成功。如果投掷硬币 n = 8 次,二项分布为 n 个试验中恰好有 k 次成功的概率分布,其中每次试验的成功概率为 p(在此示例中为 0.6)。


在八次投掷中恰好出现八次正面和零次反面的概率就是连续出现八次正面的概率,即:


Pr(X = 8) = 0.6 * 0.6 * 0.6 * 0.6 * 0.6 * 0.6 * 0.6 * 0.6 = (0.6)^8 * (0.4)^0 = 0.0168


若要在八次投掷中恰好出现七次正面,可以在任意八次投掷中出现七次正面和一次反面。有八种组合:


Pr(X = 7) = Choose(8, 1) * [ (0.6)^7 * (0.4)^1 ] = 8 * 0.0280 * 0.4 = 0.0896


下面是在 n 个试验中恰好有 k 次成功的概率的常规公式,其中 p 是每次试验的成功概率:


P(X = k) = Choose(n, k) * p^k * (1-p)^n-k


在符号检验中,p 始终是 0.5,所以 1-p 也始终是 0.5,公式就简化为:


P(X = k) = Choose(n, k) * (0.5)^n


因此,对于演示数据,存在 n = 8 个试验(数据对)和 k = 6 次成功(体重下降),所以恰好六次成功的概率是:


P(X = 6) = Choose(8, 6) * (0.5)^8 = 28 * 0.0039 = 0.1094


图 4 展示了在八次试验中当 p = 0.5 时恰好零到八次成功对应的概率图。

实现返回二项分布概率的函数很简单:


static double BinomProb(int k, int n, double p)
{// Probability of k "successes" in n trials  // if p is prob of success on a single trial  BigInteger c = Choose(n, k);double left = Math.Pow(p, k);double right = Math.Pow(1.0 - p, n - k);return (double)c * left * right;
}


图 4:n = 8 和 p = 0.5 时的二项分布

演示定义了接受 p 作为参数的通用二项函数。另一种做法是,定义一个假设 p = 0.5 的版本并简化计算,如前所述。演示不含错误检查功能。例如,在生产环境中,可能需要确保 k <= n;k 和 n 都不是负数;p 介于 0.0 到 1.0 之间。


  实现符号检验  

符号检验旨在计算无效概率。从概念上讲,这意味着前后值之间的任何差异纯粹是偶然。从数学上讲,这意味着上升或下降的概率为 0.5。


符号检验假设无效,然后计算在此假设下本该发生的成功项观测数的概率。对于八次试验中有六次成功(体重下降)的演示数据示例,计算的不是你可能会猜到的恰好六次成功的概率,而是六次以上成功的概率。这种思路相当微妙。


计算 k 次以上成功的概率有时被称为右尾检验。因此,若要实现符号检验,需要计算 k 次以上成功的概率,方法为计算恰好 k 次成功外加 K+1 次成功、K+2 次成功(依此类推)的概率。演示实现如下所示:


static double BinomRightTail(int k, int n, double p)
{// Probability of k or more successes in n trials  double sum = 0.0;for (int i = k; i <= n; ++i)sum += BinomProb(i, n, p);return sum;
}


完成符号检验只需用于统计成功次数和显示值的可选函数。演示将计数方法定义为:


static int[] DoCounts(double[] before, double[] after)
{int[] result = new int[3];for (int i = 0; i < before.Length; ++i) {if (after[i] > before[i])++result[0];  // Fail    else if (after[i] < before[i])++result[2]; // Success    else      ++result[0]; // Neither  }return result;
}


帮助程序显示方法为:


static void ShowVector(string pre, double[] v, int dec, string post)
{Console.Write(pre);for (int i = 0; i < v.Length; ++i)Console.Write(v[i].ToString("F" + dec) + " ");Console.WriteLine(post);
}


另一种设计是将成功失败计数与二项计算合并为一个更大的元方法。


  总结  

应务必谨慎解读符号检验的结果。最好解读为“符号检验表明有效”,而不是“有效”。


示例问题被称为单侧或单尾检验。因为此示例涉及的是减肥实验,所以体重下降(成功项数)大于偶然值视为有效。


还可以执行双侧(亦称为“双尾”)符号检验。例如,假设要做的是某种止痛药的实验。在实验过程中,称量检验对象的实验前后体重。没有理由相信止痛药会影响体重。换句话说,体重下降或上升都可以视为有效。


符号检验最棘手的地方是要确保定义明确。由于每个问题都存在多重对称性,因此可能会产生混淆。可以将成功定义为后值增加或减少。


例如,在减肥示例中,将后值减少定义为成功。不过,如果数据表示的是学习前后某类考试的成绩,很可能会将后值增加定义为成功。


符号检验就是所谓的非参数统计检验示例。也就是说,在某种程度上,符号检验不对研究的数据分布作任何假设。可以使用所谓的配对 t 检验来取代符号检验。


然而,t 检验假设总体数据呈正态分布(高斯分布、钟形分布),用小数据集进行验证几乎是不可能的。正因为如此,我通常会在要调查需前后对比的数据时使用符号检验,而不是配对 t 检验。




Dr.James McCaffrey 供职于华盛顿地区雷蒙德市沃什湾的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和 Bing。Scripto可通过 jammc@microsoft.com 与 McCaffrey 取得联系。


衷心感谢 Microsoft 技术专家对本文的审阅: Chris Lee 和 Kirk Olynyk


原文地址:https://msdn.microsoft.com/zh-cn/magazine/mt793273


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序人生

在大家眼里程序员是什么样子的呢&#xff1f;是每天不分日夜的在打代码&#xff1f;还是忙来忙去的帮着被人修电脑&#xff0c;调路由器&#xff1f;还是&#xff1f;或者&#xff1a;哈哈哈&#xff0c;作为一个程序员&#xff0c;我是这样的&#xff1a;下班之后先做饭&#…

2020蓝桥杯省赛---java---B---7(分类计数)

题目描述 代码实现 package com.atguigu.lanqiao;import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);long a sc.nextLong();while (a>1){System.out.print(a" ");a/2;}} }

dw怎么在框架中加入网页_怎样使用iframe,在网页中插入页面

首先&#xff0c;我们使用DreamWeaver新建一个站点&#xff0c;将我们需要归纳到一个页面的网页都包含在这个站点里面。然后我们找到一个需要嵌入页面的html文件&#xff0c;使用DW软件打开&#xff0c;找到需要插入页面的位置。3然后我们输入标签。在前半个标签的里面&#xf…

ps中将图片拖不进ps的编辑区的解决方法

今天在学习ps的过程中&#xff0c;发现我的ps怎么和人家老师的不一样&#xff0c;怎么不一样呢&#xff1f;人家老师的ps5中&#xff0c;鼠标可以直接拖到ps的编辑栏中&#xff0c;可是我的死活拖不进去。怎么办&#xff1f;怎么办&#xff1f;经过自己瞎鼓捣和上网查&#xff…

MySQL avg()函数

转载自 MySQL avg()函数 MySQL AVG()函数简介 MySQL AVG()函数是一个聚合函数&#xff0c;它用于计算一组值或表达式的平均值。 AVG()函数的语法如下&#xff1a; AVG(DISTINCT expression)您可以使用AVG()函数中的DISTINCT运算符来计算不同值的平均值。 例如&#xff0c;…

.Net Core迁移到MSBuild的多平台编译问题

一、前言 本篇主要讨论.NET Core应用程序项目结构的主题&#xff0c;重点探索.NET Core应用程序的多平台编译问题&#xff0c;这里指的多平台是指.NET Framework、.NET Core App、.NET Standard、Mono、UWP等多平台的条件编译、项目&#xff08;包&#xff09;引用、编译符号等…

strcompare php,PHP中的startswith()和endsWith()函数

泛舟湖上清波郎朗功能function substr_startswith($haystack, $needle) {return substr($haystack, 0, strlen($needle)) $needle;}function preg_match_startswith($haystack, $needle) {return preg_match(~ . preg_quote($needle, ~) . ~A, $haystack) > 0;}function su…

2020蓝桥杯省赛---java---B---8(走方格)

题目描述 【问题描述】在平面上有一些二维的点阵。 这些点的编号就像二维数组的编号一样&#xff0c;从上到下依次为第 1 至第 n 行&#xff0c;从左到右依次为第 1 至第 m 列&#xff0c;每一个点可以用行号和列号来表示。现在有个人站在第 1 行第 1 列&#xff0c;要走到第 …

我是一位老师,讲课是我的乐趣,可是……

我是一位老师&#xff0c;讲课是我的乐趣&#xff0c;但是作为一个班的老师除了给学生讲课之外&#xff0c;我们还需要其他的管理&#xff0c;比如控班&#xff0c;比如管理学生、指导学生&#xff0c;学生不做作业怎么办&#xff0c;学生不听课怎么办&#xff1f;或者学生上课…

MySQL max()函数

转载自 MySQL max()函数 MySQL MAX函数介绍 MySQL MAX()函数返回一组值中的最大值。MAX()函数在许多查询中非常方便&#xff0c;例如查找最大数量&#xff0c;最昂贵的产品以及客户的最大付款。 MAX()函数的语法如下&#xff1a; MAX(DISTINCT expression);如果添加DISTIN…

当我们在谈大前端的时候,我们谈的是什么

在今天&#xff0c;大前端并不是一个陌生的词汇&#xff0c;我们偶尔会听人谈起它&#xff0c;前些天还看到卓同学写了一篇《大前端时代下App开发者的生存之道》&#xff0c;说明这个词开始成为某种共识了。 但是大前端到底指的是什么&#xff1f;事实上大前端并没有明确的定义…

153. 寻找旋转排序数组中的最小值---LeetCode---JAVA

class Solution {public int findMin(int[] nums) {int minnums[0];for(int i1;i<nums.length;i){if(nums[i]<min){return nums[i];} }return nums[0];} }

php破坏代码,php不破坏单词截取子字符串

php不破坏单词截取子字符串/*snippet(phrase,[max length],[phrase tail])snippetgreedy(phrase,[max length before next space],[phrase tail])*/function snippet($text,$length64,$tail"...") {$text trim($text);$txtl strlen($text);if($txtl > $length) …

hibernate中报错could not initialize proxy - no Session的解决方法

hibernate中报错&#xff1a;could not initialize proxy - no Session&#xff0c;怎么解决&#xff1f; 解决方法&#xff1a; 在该hbm文件中的 many-to-one 的最后加上&#xff1a;lazy”false”&#xff0c;请看&#xff1a; <many-to-one name"kecheng" cl…

MySQL group_concat()函数

转载自 MySQL group_concat()函数 MySQL GROUP_CONCAT函数介绍 MySQL GROUP_CONCAT()函数将组中的字符串连接成为具有各种选项的单个字符串。 下面说明了GROUP_CONCAT()函数的语法&#xff1a; GROUP_CONCAT(DISTINCT expressionORDER BY expressionSEPARATOR sep);以下是…

MySQL标准偏差

转载自 MySQL标准偏差 标准差介绍 标准差是衡量数据集中值的分布情况&#xff0c;标准偏差显示平均值(平均值)存在多少变化。 低标准偏差表明数据集中的值接近于平均值。 而高标准偏差表示数据集的值在大范围的值上分散。 标准偏差是方差的平方根&#xff0c;可以通过以下…

从Visual Studio看微软20年技术变迁

前言 这个世界从来都不缺变革&#xff0c;从工业革命到晶体管和集成电路&#xff0c;从生活电器到物联网&#xff0c;从简陋人机到精致体验&#xff0c;我们在享受技术带来的便捷的同时&#xff0c;也在为复杂设计而带来的挑战和生产力下降而痛并快乐着。而迫切期盼的&#xff…

c传给php数据解包,小程序源码提取工具,完美解包,一键提取小程序源代码工具_PHP源码...

请注意&#xff1a;该源码来源网友分享&#xff0c;搜库资源网不提供技术支持&#xff0c;没有技术能力的小白勿拍。(如需安装服务费用另算)直接解压后就可以使用将小程序文件放到 wxapkg目录下然后打开 CrackMinApp.exe 按说明即可使用那么如何才能在手机里找到小程序的源文件…

2015蓝桥杯省赛---java---C---3(无穷分数)

题目描述 思路分析 它的结果会随着运算的次数越多,会越来越精确一个值,我们只需要把那个大约值求出来即可. 代码实现 package com.atguigu.lanqiao;public class Main {public static void main(String[] args) {System.out.printf("%.5f",f(1));}public static d…

致给博客粉丝

最近一段时间是我最忙的一段时间&#xff0c;也是博客粉丝找我最多的一段时间&#xff0c;好多粉丝找我要人脸识别的jar包和js文件&#xff0c;在这里我给大家统一说一下&#xff0c;粉丝们可以直接关注微信公众号&#xff1a;青鸟IT汇&#xff0c;回复&#xff1a;java人脸识别…