笨办法学C 练习43:一个简单的统计引擎
练习43:一个简单的统计引擎
原文:Exercise 43: A Simple Statistics Engine
译者:飞龙
这是一个简单的算法,我将其用于“联机”(不储存任何样本)收集概要统计。我在任何需要执行一些统计,比如均值、标准差和求和中使用它,但是其中我并不会储存所需的全部样本。我只需要储存计算出的结果,它们仅仅含有5个数值。
计算标准差和均值
首先你需要一系列样本。它可以使任何事情,比如完成一个任务所需的时间,某人访问某个东西的次数,或者甚至是网站的评分。是什么并不重要,只要你能得到一些数字,并且你想要知道它们的下列概要统计值:
sum
对所有数字求和。
sumsq
(平方和)
对所有数字求平方和。
count(n)
求出样本数量。
min
求出样本最小值。
max
求出样本最大值。
mean
求出样本的均值。它类似于但又不是中位数,但可作为中位数的估计。
stddev
使用$sqrt(sumsq - (sum * mean) / (n - 1) )
来计算标准差,其中sqrt
为math.h
头文件中的平方根。
我将会使用R来验证这些计算,因为我知道R能够计算正确。
> s <- runif(n=10, max=10)
> s[1] 6.1061334 9.6783204 1.2747090 8.2395131 0.3333483 6.9755066 1.0626275[8] 7.6587523 4.9382973 9.5788115
> summary(s)Min. 1st Qu. Median Mean 3rd Qu. Max. 0.3333 2.1910 6.5410 5.5850 8.0940 9.6780
> sd(s)
[1] 3.547868
> sum(s)
[1] 55.84602
> sum(s * s)
[1] 425.1641
> sum(s) * mean(s)
[1] 311.8778
> sum(s * s) - sum(s) * mean(s)
[1] 113.2863
> (sum(s * s) - sum(s) * mean(s)) / (length(s) - 1)
[1] 12.58737
> sqrt((sum(s * s) - sum(s) * mean(s)) / (length(s) - 1))
[1] 3.547868
>
你并不需要懂得R,只需要看着我拆分代码来解释如何检查这些运算:
lines 1-4
我使用runit
函数来获得“随机形式”的数字分布,之后将它们打印出来。我会在接下来的单元测试中用到它。
lines 5-7
这个就是概要,便于你看到R如何计算它们。
lines 8-9
这是使用sd
函数计算的stddev
。
lines 10-11
现在我开始手动进行这一计算,首先计算sum
。
lines 12-13
stddev
公式中的下一部分是sumsq
,我可以通过sum(s * s)
来得到,它告诉R将整个s
列表乘以其自身,之后计算它们的sum
。R的可以在整个数据结构上做运算,就像这样。
lines 14-15
观察那个公式,我之后需要sum
乘上mean
,所以我执行了sum(s) * mean(s)
。
lines 16-17
我接着将sumsq
参与运算,得到sum(s * s) - sum(s) * mean(s)
。
lines 18-19
还需要除以n - 1
,所以我执行了(sum(s * s) - sum(s) * mean(s)) / (length(s) - 1)
。
lines 20-21
随后,我使用sqrt
算出平方根,并得到3.547868,它符合R通过sd
的运算结果。
实现
这就是计算stddev
的方法,现在我可以编写一些简单的代码来实现这一计算。
#ifndef lcthw_stats_h
#define lctwh_stats_htypedef struct Stats {double sum;double sumsq;unsigned long n;double min;double max;
} Stats;Stats *Stats_recreate(double sum, double sumsq, unsigned long n, double min, double max);Stats *Stats_create();double Stats_mean(Stats *st);double Stats_stddev(Stats *st);void Stats_sample(Stats *st, double s);void Stats_dump(Stats *st);#endif
这里你可以看到我将所需的统计量放入一个struct,并且创建了用于处理样本和获得数值的函数。实现它只是转换数字的一个练习:
#include <math.h>
#include <lcthw/stats.h>
#include <stdlib.h>
#include <lcthw/dbg.h>Stats *Stats_recreate(double sum, double sumsq, unsigned long n, double min, double max)
{Stats *st = malloc(sizeof(Stats));check_mem(st);st->sum = sum;st->sumsq = sumsq;st->n = n;st->min = min;st->max = max;return st;error:return NULL;
}Stats *Stats_create()
{return Stats_recreate(0.0, 0.0, 0L, 0.0, 0.0);
}double Stats_mean(Stats *st)
{return st->sum / st->n;
}double Stats_stddev(Stats *st)
{return sqrt( (st->sumsq - ( st->sum * st->sum / st->n)) / (st->n - 1) );
}void Stats_sample(Stats *st, double s)
{st->sum += s;st->sumsq += s * s;if(st->n == 0) {st->min = s;st->max = s;} else {if(st->min > s) st->min = s;if(st->max < s) st->max = s;}st->n += 1;
}void Stats_dump(Stats *st)
{fprintf(stderr, "sum: %f, sumsq: %f, n: %ld, min: %f, max: %f, mean: %f, stddev: %f",st->sum, st->sumsq, st->n, st->min, st->max,Stats_mean(st), Stats_stddev(st));
}
下面是 stats.c
中每个函数的作用:
Stats_recreate
我希望从一些数据中加载这些数据,这和函数让我重新创建Stats
结构体。
Stats_create
只是以全0的值调用Stats_recreate
。
Stats_mean
使用sum
和n
计算均值。
Stats_stddev
实现我之前的公式,唯一的不同就是我使用t->sum / st->n
来计算均值,而不是调用Stats_mean
。
Stats_sample
它用于在Stats
结构体中储存数值。当你向它提供数值时,它看到n
是0,并且相应地设置min
和max
。之后的每次调用都会使sum
、sumsq
和n
增加,并且计算出这一新的样本的min
和max
值。
Stats_dump
简单的调试函数,用于转储统计量,便于你看到它们。
我需要干的最后一件事,就是确保这些运算正确。我打算使用我的样本,以及来自于R会话中的计算结果创建单元测试,来确保我会得到正确的结果。
#include "minunit.h"
#include <lcthw/stats.h>
#include <math.h>const int NUM_SAMPLES = 10;
double samples[] = {6.1061334, 9.6783204, 1.2747090, 8.2395131, 0.3333483,6.9755066, 1.0626275, 7.6587523, 4.9382973, 9.5788115
};Stats expect = {.sumsq = 425.1641,.sum = 55.84602,.min = 0.333,.max = 9.678,.n = 10,
};
double expect_mean = 5.584602;
double expect_stddev = 3.547868;#define EQ(X,Y,N) (round((X) * pow(10, N)) == round((Y) * pow(10, N)))char *test_operations()
{int i = 0;Stats *st = Stats_create();mu_assert(st != NULL, "Failed to create stats.");for(i = 0; i < NUM_SAMPLES; i++) {Stats_sample(st, samples[i]);}Stats_dump(st);mu_assert(EQ(st->sumsq, expect.sumsq, 3), "sumsq not valid");mu_assert(EQ(st->sum, expect.sum, 3), "sum not valid");mu_assert(EQ(st->min, expect.min, 3), "min not valid");mu_assert(EQ(st->max, expect.max, 3), "max not valid");mu_assert(EQ(st->n, expect.n, 3), "max not valid");mu_assert(EQ(expect_mean, Stats_mean(st), 3), "mean not valid");mu_assert(EQ(expect_stddev, Stats_stddev(st), 3), "stddev not valid");return NULL;
}char *test_recreate()
{Stats *st = Stats_recreate(expect.sum, expect.sumsq, expect.n, expect.min, expect.max);mu_assert(st->sum == expect.sum, "sum not equal");mu_assert(st->sumsq == expect.sumsq, "sumsq not equal");mu_assert(st->n == expect.n, "n not equal");mu_assert(st->min == expect.min, "min not equal");mu_assert(st->max == expect.max, "max not equal");mu_assert(EQ(expect_mean, Stats_mean(st), 3), "mean not valid");mu_assert(EQ(expect_stddev, Stats_stddev(st), 3), "stddev not valid");return NULL;
}char *all_tests()
{mu_suite_start();mu_run_test(test_operations);mu_run_test(test_recreate);return NULL;
}RUN_TESTS(all_tests);
这个单元测试中没什么新东西,除了EQ
宏。我比较懒,并且不想查询比较两个double
值的标准方法,所以我使用了这个宏。double
的问题是等性不是完全相等,因为我使用了两个不同的系统,并带有不同的四舍五入的位数。解决方案就是判断两个数“乘以10的X次方是否相等”。
我使用EQ
来计算数字的10的幂,之后使用round
函数来获得证书。这是个简单的方法来四舍五入N位小数,并以整数比较结果。我确定有数以亿计的其它方法能做相同的事情,但是现在我就用这种。
预期结果储存在Stats
struct
中,之后我只是确保我得到的数值接近R给我的数值。
如何使用
你可以使用标准差和均值来决定一个新的样本是否是“有趣”的,或者你可以使用它们计算统计量的统计量。前者对于人们来说更容易理解,所以我用登录的例子来做个简短的解释。
假设你在跟踪人们花费多长时间在一台服务器上,并且你打算用统计来分析它。每次有人登录进来,你都对它们在这里的时长保持跟踪,之后调用Stats_sample
函数。我会寻找停留“过长”时间的人,以及“过短”的人。
比起设定特殊的级别,我更倾向于将一个人的停留时间与mean (plus or minus) 2 * stddev
这个范围进行比较。我计算出mean
和2 * stddev
,并且如果它们在这个范围之外,我就认为是“有趣”的。由于我使用了联机算法来维护这些统计量,所以它非常快,并且我可以使软件标记在这个范围外的用户。
这不仅仅用于找出行为异常的用户,更有助于标记一些潜在的问题,你可以查看它们来观察发生了什么。它基于所有用户的行为来计算,这也避免了你任意挑出一个数值而并不基于实际情况的问题。
你可以从中学到的通用规则是,mean (plus or minus) 2 * stddev
是90%的值预期所属的范围预测值,任何在它之外的值都是有趣的。
第二种利用这些统计量的方式就是继续将其用于其它的Stats
计算。基本上像通常一样使用Stats_sample
,但是之后在min
、max
、n
、mean
和stddev
上执行Stats_sample
。这会提供二级的度量,并且让你对比样本的样本。
被搞晕了吗?我会以上面的例子基础,并且假设你拥有100台服务器,每台都运行一个应用。你已经在每个应用服务器上跟踪了用户的登录时长,但是你想要比较所有的这100和应用,并且标记它们当中任何登录时间过长的用户。最简单的方式就是每次有人登录进来时,计算新的登录统计量,之后将Stats structs
的元素添加到第二个Stats
中。
你最后应该会得到一些统计量,它们可以这样命名:
均值的均值
这是一个Stats struct
,它向你提供所有服务器的均值的mean
和stddev
。你可以用全局视角来观察任何在此之外的用户或服务器。
标准差的均值
另一个Stats struct
,计算这些服务器的分布的统计量。你之后可以分析每个服务器并且观察是否它们中的任何服务器具有异常分散的分布,通过将它们的stddev
和这个mean of stddevs
统计量进行对比。
你可以计算出全部统计量,但是这两个是最有用的。如果你打算监视服务器上的移除登录时间,你可以这样做:
用户John登录并登出服务器A。获取服务器A的统计量,并更新它们。
获取
mean of means
统计量,计算出A的均值并且将其加入样本。我叫它m_of_m
。获取
mean of stddev
统计量,将A的标准差添加到样本中。我叫它m_of_s
。如果A的
mean
在m_of_m.mean + 2 * m_of_m.stddev
范围外,标记它可能存在问题。如果A的
stddev
在m_of_s.mean + 2 * m_of_s.stddev
范围外,标记它可能存在行为异常。最后,如果John的登录时长在A的范围之外,或A的
m_of_m
范围之外,标记为有趣的。
通过计算“均值的均值”,或者“标准差的均值”,你可以以最小的执行和储存总量,有效地跟踪许多度量。
附加题
将
Stats_stddev
和Stats_mean
转换为static inline
函数,放到stats.h
文件中,而不是stats.c
文件。使用这份代码来编写
string_algos_test.c
的性能测试。使它为可选的,并且运行基准测试作为一系列样本,之后报告结果。编写它的另一个语言的版本。确保这个版本基于我的数据正确执行。
编写一个小型程序,它能从文件读取所有数字,并执行这些统计。
使程序接收一个数据表,其中第一行是表头,剩下的行含有任意数量空格分隔的数值。你的程序应该按照表头中的名称,打印出每一列的统计值。
笨办法学C 练习43:一个简单的统计引擎相关推荐
- 笔记37 笨办法学python练习43面向对象OOP的游戏代码(二)代码的反复理解
笔记37 笨办法学python练习43面向对象OOP的游戏代码(二)代码的反复理解 连续贴着这个练习43的代码折腾了整整两天,把那些英文文本翻译为中文文本,重新装进这个代码之中.本想一段一段的运行,发 ...
- 笔记36 笨办法学python练习43面向对象OOP的文字理解(一)
笔记36 笨办法学python练习43面向对象OOP的文字理解(一) 先仔细看了本练习的文本,感到一个笔记写不下来这个复杂的练习过程,那就先把文字理解的部分先来完成,再用一篇笔记来完成代码的理解. 一 ...
- 如何用python画小熊_转载:《笨办法学Python》笔记-----一个项目骨架
骨架目录 为什么要建立这么个骨架? 建立一个项目的骨架目录就如同代码风格,统一规范的项目骨架目录应当是能提高项目的可读性的,进而为后来人提供快速方便的项目维护参考,降低项目维护的成本. 基本的框架包括 ...
- 笨办法学 Python · 续 第二部分:简单的黑魔法
第二部分:简单的黑魔法 原文:Part II: Quick Hacks 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你有最好的想法,你会打动世界!你会成为一个亿万富豪!你的大 ...
- 计算机编程书籍-笨办法学Python 3:基础篇+进阶篇
编辑推荐: 适读人群 :本书适合所有已经开始使用Python的技术人员,包括初级开发人员和已经升级到Python 3.6版本以上的经验丰富的Python程序员. "笨办法学"系列, ...
- python教程第四版pdf下载-笨办法学python 第四版 中文pdf高清版
笨办法学 Python是Zed Shaw 编写的一本Python入门书籍.适合对计算机了解不多,没有学过编程,但对编程感兴趣的朋友学习使用.这本书以习题的方式引导读者一步一步学习编 程,从简单的打印一 ...
- 笨办法学python pdf 第三版_笨办法学python第三版
笨办法学python第三版pdf电子书是一本Python学习参考书,是美国程序员Zed A.Shaw编著,通过简单通俗的方法,结合内部的集体,让程序员学懂python,适用于初级学习python编程的 ...
- python教程第四版pdf下载-笨办法学python第四版
笨办法学python第四版是由Zed Shaw所编写的一本书.如果你还是Python新手,那么这是一本非常不错的入门书籍.书本里以习题方式,引导读者慢慢学会了编程. 目录: 习题 0: 准备工作 习题 ...
- python教程第四版pdf下载-笨办法学python第四版 电子书(pdf格式)
笨办法学python第四版是由Zed Shaw所编写的一本书.如果你还是Python新手,那么这是一本非常不错的入门书籍.书本里以习题方式,引导读者慢慢学会了编程. 目录: 习题 0: 准备工作 习题 ...
最新文章
- 030_SpringBoot全局属性配置文件
- Bootstrap table后端分页(ssm版)
- 负载均衡集群介绍LVS介绍LVS调度算法LVS NAT模式搭建
- Android Binder 分析——匿名共享内存(好文)
- 每日算法系列【LeetCode 1039】多边形三角剖分的最低得分
- Windows 环境安装 OS X Monaco 字体
- mac系统vscode头文件not found
- 【vscode】——程序运行时添加环境变量
- 苹果cmsv10动漫二次元主题网站模板免费源码
- ipad显示连接不到商店服务器,iPad无法连接App Store 打不开怎么办
- IE10,带您走进HTML5时代
- javascript 中的onblur 事件
- 入职培训ppt计算机二级,计算机二级office试题PPT.doc
- 盼望着,盼望着,东风来了,春天的脚步近了。
- 一张图读懂PBN旁切转弯计算
- 计算机考研复试-离散数学
- MailStore Server标准的电子邮件归档
- LeetCode.天际线问题
- Mysql 性能优化神器Explain详解
- 九度OJ学习笔记 题目1186
热门文章
- java utf8 简繁转换 类库_在Java中进行中文繁体简体转换,基于OpenCC(Open Chinese Convert)方案...
- 广义典型相关分析_一文教你掌握广义估计方程
- python中io中的+模式_Python----文件的IO操作
- 多边形区域填充算法_花一分钟看一个案例,PPT中图片填充形状的应用
- python模式字符串_使用python进行字符串模式匹配
- iconfont 无法导入 svg_Figma绘制图标上传至iconfont的正确姿势
- android里build报错怎么办,Android Studio 当build时候出错解决办法
- pytorch 对抗样本_【天池大赛】通用目标检测的对抗攻击方法一览
- python3.6网络爬虫_python3.6网络爬虫
- 嵌入式设备中支持国密算法的方法——移植Miracl库的步骤说明