#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int shoudsave=0/* */
struct student
{
char num[10]/* 学号 */
char name[20]
char sex[4]
int cgrade
int mgrade
int egrade
int totle
int ave
char neartime[10]/* 最近更新时间 */
}
typedef struct node
{
struct student data
struct node *next
}Node,*Link
void menu()
{
printf("********************************************************************************")
printf("\t1登记学生资料\t\t\t\t\t2删除学生资料\n")
printf("\t3查询学生资料\t\t\t\t\t4修改学生资料\n")
printf("\t5保存学生资料\t\t\t\t\t0退出系统\n")
printf("********************************************************************************\n")
}
void printstart()
{
printf("-----------------------------------------------------------------------\n")
}
void Wrong()
{
printf("\n=====>提示:输入错误!\n")
}
void Nofind()
{
printf("\n=====>提示:没有找到该学生!\n")
}
void printc() /* 本函数用于输出中文 */
{
printf(" 学号\t 姓名 性别 英语成绩 数学成绩 C语言成绩 总分 平均分\n")
}
void printe(Node *p)/* 本函数用于输出英文 */
{
printf("%-12s%s\t%s\t%d\t%d\t%d\t %d\t %d\n",p->data.num,p->data.name,p->data.sex,p->data.egrade,p->data.mgrade,p->data.cgrade,p->data.totle,p->data.ave)
}
Node* Locate(Link l,char findmess[],char nameornum[]) /* 该函数用于定位连表中符合要求的接点,并返回该指针 */
{
Node *r
if(strcmp(nameornum,"num")==0) /* 按学号查询 */
{
r=l->next
while(r!=NULL)
{
if(strcmp(r->data.num,findmess)==0)
return r
r=r->next
}
}
else if(strcmp(nameornum,"name")==0) /* 按姓名查询 */
{
r=l->next
while(r!=NULL)
{
if(strcmp(r->data.name,findmess)==0)
return r
r=r->next
}
}
return 0
}
void Add(Link l) /* 增加学生 */
{
Node *p,*r,*s
char num[10]
r=l
s=l->next
while(r->next!=NULL)
r=r->next/* 将指针置于最末尾 */
while(1)
{
printf("请你输入学号(以'0'返回上一级菜单:)")
scanf("%s",num)
if(strcmp(num,"0")==0)
break
while(s)
{
if(strcmp(s->data.num,num)==0)
{
printf("=====>提示:学号为'%s'的学生已经存在,若要修改请你选择'4 修改'!\n",num)
printstart()
printc()
printe(s)
printstart()
printf("\n")
return
}
s=s->next
}
p=(Node *)malloc(sizeof(Node))
strcpy(p->data.num,num)
printf("请你输入姓名:")
scanf("%s",p->data.name)
getchar()
printf("请你输入性别:")
scanf("%s",p->data.sex)
getchar()
printf("请你输入c语言成绩:")
scanf("%d",&p->data.cgrade)
getchar()
printf("请你输入数学成绩:")
scanf("%d",&p->data.mgrade)
getchar()
printf("请你输入英语成绩:")
scanf("%d",&p->data.egrade)
getchar()
p->data.totle=p->data.egrade+p->data.cgrade+p->data.mgrade
p->data.ave=p->data.totle / 3
/* 信息输入已经完成 */
p->next=NULL
r->next=p
r=p
shoudsave=1
}
}
void Qur(Link l) /* 查询学生 */
{
int sel
char findmess[20]
Node *p
if(!l->next)
{
printf("\n=====>提示:没有资料可以查询!\n")
return
}
printf("\n=====>1按学号查找\n=====>2按姓名查找\n")
scanf("%d",&sel)
if(sel==1)/* 学号 */
{
printf("请你输入要查找的学号:")
scanf("%s",findmess)
p=Locate(l,findmess,"num")
if(p)
{
printf("\t\t\t\t查找结果\n")
printstart()
printc()
printe(p)
printstart()
}
else
Nofind()
}
else if(sel==2) /* 姓名 */
{
printf("请你输入要查找的姓名:")
scanf("%s",findmess)
p=Locate(l,findmess,"name")
if(p)
{
printf("\t\t\t\t查找结果\n")
printstart()
printc()
printe(p)
printstart()
}
else
Nofind()
}
else
Wrong()
}
void Del(Link l) /* 删除 */
{
int sel
Node *p,*r
char findmess[20]
if(!l->next)
{
printf("\n=====>提示:没有资料可以删除!\n")
return
}
printf("\n=====>1按学号删除\n=====>2按姓名删除\n")
scanf("%d",&sel)
if(sel==1)
{
printf("请你输入要删除的学号:")
scanf("%s",findmess)
p=Locate(l,findmess,"num")
if(p)
{
r=l
while(r->next!=p)
r=r->next
r->next=p->next
free(p)
printf("\n=====>提示:该学生已经成功删除!\n")
shoudsave=1
}
else
Nofind()
}
else if(sel==2)
{
printf("请你输入要删除的姓名:")
scanf("%s",findmess)
p=Locate(l,findmess,"name")
if(p)
{
r=l
while(r->next!=p)
r=r->next
r->next=p->next
free(p)
printf("\n=====>提示:该学生已经成功删除!\n")
shoudsave=1
}
else
Nofind()
}
else
Wrong()
}
void Modify(Link l)
{
Node *p
char findmess[20]
if(!l->next)
{
printf("\n=====>提示:没有资料可以修改!\n")
return
}
printf("请你输入要修改的学生学号:")
scanf("%s",findmess)
p=Locate(l,findmess,"num")
if(p)
{
printf("请你输入新学号(原来是%s):",p->data.num)
scanf("%s",p->data.num)
printf("请你输入新姓名(原来是%s):",p->data.name)
scanf("%s",p->data.name)
getchar()
printf("请你输入新性别(原来是%s):",p->data.sex)
scanf("%s",p->data.sex)
printf("请你输入新的c语言成绩(原来是%d分):",p->data.cgrade)
scanf("%d",&p->data.cgrade)
getchar()
printf("请你输入新的数学成绩(原来是%d分):",p->data.mgrade)
scanf("%d",&p->data.mgrade)
getchar()
printf("请你输入新的英语成绩(原来是%d分):",p->data.egrade)
scanf("%d",&p->data.egrade)
p->data.totle=p->data.egrade+p->data.cgrade+p->data.mgrade
p->data.ave=p->data.totle/3
printf("\n=====>提示:资料修改成功!\n")
shoudsave=1
}
else
Nofind()
}
void Disp(Link l)
{
int count=0
Node *p
p=l->next
if(!p)
{
printf("\n=====>提示:没有资料可以显示!\n")
return
}
printf("\t\t\t\t显示结果\n")
printstart()
printc()
printf("\n")
while(p)
{
printe(p)
p=p->next
}
printstart()
printf("\n")
}
void Tongji(Link l)
{
Node *pm,*pe,*pc,*pt,*pa/* 用于指向分数最高的接点 */
Node *r=l->next
if(!r)
{
printf("\n=====>提示:没有资料可以统计!\n")
return
}
pm=pe=pc=pt=pa=r
while(r!=NULL)
{
if(r->data.cgrade>=pc->data.cgrade)
pc=r
if(r->data.mgrade>=pm->data.mgrade)
pm=r
if(r->data.egrade>=pe->data.egrade)
pe=r
if(r->data.totle>=pt->data.totle)
pt=r
if(r->data.ave>=pa->data.ave)
pa=r
r=r->next
}
printf("------------------------------统计结果--------------------------------\n")
printf("总分最高者:\t%s %d分\n",pt->data.name,pt->data.totle)
printf("平均分最高者:\t%s %d分\n",pa->data.name,pa->data.ave)
printf("英语最高者:\t%s %d分\n",pe->data.name,pe->data.egrade)
printf("数学最高者:\t%s %d分\n",pm->data.name,pm->data.mgrade)
printf("c语言最高者:\t%s %d分\n",pc->data.name,pc->data.cgrade)
printstart()
}
void Sort(Link l)
{
Link ll
Node *p,*rr,*s
ll=(Link)malloc(sizeof(Node))/* 用于做新的连表 */
ll->next=NULL
if(l->next==NULL)
{
printf("\n=====>提示:没有资料可以排序!\n")
return
}
p=l->next
while(p)
{
s=(Node*)malloc(sizeof(Node))/* 新建接点用于保存信息 */
s->data=p->data
s->next=NULL
rr=ll
while(rr->next!=NULL &&rr->next->data.totle>=p->data.totle)
rr=rr->next
if(rr->next==NULL)
rr->next=s
else
{
s->next=rr->next
rr->next=s
}
p=p->next
}
free(l)
l->next=ll->next
printf("\n=====>提示:排序已经完成!\n")
}
void Save(Link l)
{
FILE* fp
Node *p
int flag=1,count=0
fp=fopen("c:\\student","wb")
if(fp==NULL)
{
printf("\n=====>提示:重新打开文件时发生错误!\n")
exit(1)
}
p=l->next
while(p)
{
if(fwrite(p,sizeof(Node),1,fp)==1)
{
p=p->next
count++
}
else
{
flag=0
break
}
}
if(flag)
{
printf("\n=====>提示:文件保存成功.(有%d条记录已经保存.)\n",count)
shoudsave=0
}
fclose(fp)
}
void main()
{
Link l/* 连表 */
FILE *fp/* 文件指针 */
int sel
char ch
char jian
int count=0
Node *p,*r
printf("\t\t\t\t学生成绩管理系统\n\t\t\t\t\n")
l=(Node*)malloc(sizeof(Node))
l->next=NULL
r=l
fp=fopen("C:\\student","rb")
if(fp==NULL)
{
printf("\n=====>提示:文件还不存在,是否创建?(y/n)\n")
scanf("%c",&jian)
if(jian=='y'||jian=='Y')
fp=fopen("C:\\student","wb")
else
exit(0)
}
printf("\n=====>提示:文件已经打开,正在导入记录......\n")
while(!feof(fp))
{
p=(Node*)malloc(sizeof(Node))
if(fread(p,sizeof(Node),1,fp)) /* 将文件的内容放入接点中 */
{
p->next=NULL
r->next=p
r=p/* 将该接点挂入连中 */
count++
}
}
fclose(fp)/* 关闭文件 */
printf("\n=====>提示:记录导入完毕,共导入%d条记录.\n",count)
while(1)
{
menu()
printf("请你选择操作:")
scanf("%d",&sel)
if(sel==0)
{
if(shoudsave==1)
{ getchar()
printf("\n=====>提示:资料已经改动,是否将改动保存到文件中(y/n)?\n")
scanf("%c",&ch)
if(ch=='y'||ch=='Y')
Save(l)
}
printf("\n=====>提示:你已经退出系统,再见!\n")
break
}
switch(sel)
{
case 1:Add(l)break/* 增加学生 */
case 2:Del(l)break/* 删除学生 */
case 3:Qur(l)break/* 查询学生 */
case 4:Modify(l)break/* 修改学生 */
case 5:Save(l)break/* 保存学生 */
case 9:printf("\t\t\t==========帮助信息==========\n")break
default: Wrong()getchar()break
}
}
}
文/Jongerden &Fu
买辆新车永远是令人兴奋的事情,尤其当这辆车还是你的第一辆车的时候。有研究显示,年轻人在挑选座驾时会更倾向选择父母曾拥有的品牌。这种趋势可能是出于品牌忠诚度,也或许是由于他们在这个品牌身上拥有美好回忆。但是,也可能仅仅是因为他们并不懂得如何在不同品牌之间做选择,于是只好选择自己熟悉的品牌。而且,哪怕是那些喜欢尝鲜,希望选购不同品牌车型的人,也会觉得现有购车网站并没太大帮助,因为它们往往会让你在搜索前就先选定品牌或型号。数据侠Steven Jongerden和Huanghaotian Fu为了弥补这一空缺,让人们能在做决定时掌握更多信息,用机器学习和数据分析打造了一个购车推荐平台。
爬数据,寻找相关性
为了更好地帮助人们选择自己的理想座驾,我们需要能够将个人需求与品牌及型号信息进行匹配的数据。
(图片说明:项目设计流程)
由于这种数据并不是公开可以获取的,只能从现有的汽车销售网站上提取。而这些网站上的数据,也代表着当今市场上正在交易的汽车的信息。我们使用Python的Beautifulsoup对一个非常流行的汽车网站进行爬取,获得了12000辆车、覆盖20种品牌和37个特征维度的数据集。我们之后搭建的推荐系统,推荐的车型也将来自上述范围。
我们首先用R语言、使用K近邻算法(K Nearest Neighbor)对缺失数据进行处理和补充。缺失最多的是油耗信息(8.6%缺失)和加速数据(6.9%缺失)。我们基于车价、汽车品牌和汽车类型,使用欧式几何距离法(Euclidean Distance)以及等于根号n的K值,通过K近邻算法进行了补充,并最终得到一个完整的数据集。由于K近邻算法是无监督机器学习算法,我们没法量化它的表现和准确度,尤其是在一个多维的解空间里(Multidimensional Solution Space)。
(图片说明:各变量的交叉相关性分析)
初步的数据分析显示出很有意思的相互关系。比如,车价和引擎型号有很强关联,高价位的汽车往往有更大的引擎。另外,数据显示,更贵的车往往更耗油。总的来看,不同维度的变量之间有很强的相关性,这让我们可以用各种机器学习算法来进行分析。
调试机器学习的算法模型
为了预测那些对潜在购车者来说很重要的特征,同时让我们的推荐平台能独立于外部数据源,我们使用机器学习算法对一些特定的特征进行了预测。
由于数据是通过特定方式收集(比如,设计相似的汽车会被放置在数据组的同一个类别下,因为在爬取时它们的数据收集是按照品牌顺序进行的),数据集中存在序列相关(Serial Correlation)现象。为了消除序列相关,我们在使用机器学习算法分析数据前,对数据组的次序进行随机排列。另外,为了验证效果,我们将数据组按照4比1的比例分成一个训练集和一个测试集。
首先,我们使用与车价高度相关的特征,搭建了一个多元线性回归模型。得出的R调整平方值(使用测试集计算得出)为0.899。在对预估模型进行Breusch-Godfrey测试后,观测到数据集是按照汽车产商和型号排列,检测到了序列相关性关系。为了解决这个问题,我们使用了开头提到的方法进行处理。另外,残差还发现存在异方差性,意味着残差中,对于不同车价来说,方差并不均等。尽管这不符合Gauss Markov假设中的一个最优线性无偏估计,但检查模型的残差图(Residual Plot)并未发现异方差性很高的残差,因此这个模型可以成立。
其次,我们使用了多元线性回归模型及前向逐步选择法(Forward Stepwise Selection)。这个算法将每个可能的模型与一个包含所有特征的模型进行比较,并选出贝叶斯信息量(Bayesian Information Criterion)最低的最佳组合。得出的R调整平方值(使用测试数据集计算得出)为0.914。
为了进一步提高线性模型的表现,我们使用一个预先设定的来代表车价,并对这个变量执行Box-Cox变换。Box-Cox变换是一种常见的数据变换,用于连续的响应变量不满足正态分布的情况,变换之后,可以一定程度上减小不可观测的误差和预测变量的相关性。经过Box-Cox变换,我们的数据更接近正态分布。这个模型得出的R调整平方值为0.9,与第一个手动调整的多元线性回归模型相比,并没有太多提升。
接下来,我们使用收缩方式,或称为正则化(Shrinkage/Regularization Method)来提高前面模型得到的数据。传统的收缩方式(Lasso回归和岭回归)在处理包含与因变量有强相关性的多元变量的数据集时,存在缺陷。假如使用Lasso回归,大多数系数会被减至0,但其实他们可能对结果有很强的解释力。而Elastic Net模型可以通过引入一个额外的超参数来平衡Lasso回归和岭回归,进而克服上面提到的问题。
此外,第二个引入的超参数则用于确定模型的均方差和复杂性之间的平衡。为了确定最佳的超参数,进而将均方差减小到最小,同时保证模型是最简单的,我使用了10折交叉验证。得出的R调整平方值为0.9218。
最后,为了进一步提升预测准确度,我使用了提升树模型(Gradient Tree Boosting Model)。它由7000个决策树组成,根据超参数的交叉验证所得的平均数,决策树的深度(Interaction Depth)等于4,收缩率(Shrinkage Factor)等于0.1。而它的R调整平方值十分惊艳,为0.9569,也就是说,我们测试的所有模型中,提升树模型拥有最强的预测能力。
不过即便提升树模型预测能力最强,人们依然更喜欢简单的模型。而鉴于简单的多元线性回归模型表现良好,简单的线性回归模型将会用于我们的推荐平台。
基于协同过滤的汽车系统
能够预测汽车价格和其他有趣的特征,可以在推荐汽车时为用户提供更多的信息。
我们的推荐基于一个基本假设:拥有相同偏好的用户做出的打分是相似的。这也意味着,如果一个人喜欢一辆特定的汽车的所有特征,那么他就在整体上喜欢这辆车。
为了找到符合某一名用户需求的车,首先需要找到与他拥有同样需求的其他用户,并将这些用户选择的对应的车型进行合并。在对这名用户与其他用户比较时,使用了K近邻算法,距离由皮尔逊相关系数或余弦相似性来确定。当这些相似用户被确定,他们对汽车的打分会被整合,并基于此为新用户做出推荐。这个过程被称为基于用户的协同过滤(User Based Collaborative Filtering)。
基于用户的协同过滤是一种半监督的机器学习技术,使用训练数据集的评分矩阵中的特定项来确定测试评分矩阵中的不确定的项。因此,我们可以评断推荐的准确度。我们使用一个7个值已确定的10折交叉验证,通过观察它的ROC和PR曲线(Precision/Recall Graphs)的平均值来评估推荐的表现情况。
ROC曲线显示了True Positive (真正,对应y轴)和False Positive (假正,对应x轴)的关系。ROC的结果显示,多数推荐是正确的,仅有一小部分的False Positives(也就是假的预测)。ROC曲线向左上角靠近,说明这个模型的预测比较准确。
(图片说明:ROC曲线)
而PR曲线则显示了准确率(搜索结果到底有多大帮助)以及召回率(结果有多完整)之间的关系。从图中可以看出,对于一小部分推荐,准确度很高;然而,随着召回率提高,准确率趋向于0。
我们可以得出结论,这个模型在靠近图形右上角(代表着完美的模型)的时候表现良好。另外,将召回率限制在最多10个推荐的时候,准确率的降低会得到遏制。
(图片说明:PR曲线)
基于这个模型以及用户对车的具体需求,我们的推荐平台给用户推荐了10个汽车品牌以及相应的型号。
通过将推荐模型和机器学习模型结合,我们搭建了一个交互界面,当用户输入信息,比如他期待的汽车引擎马力、汽车类型等,系统就可以做出推荐。这些用户输入信息可以通过下拉菜单以及勾选等方式手动控制。此外,你还可以用一句话描述你的理想座驾,而推荐模型则会基于此为你推荐10辆你可能喜欢的汽车,它们的车价、排气量、油耗以及图片等信息也会展示出来,供用户进行简单比较。
当用户勾选“喜欢”,界面会重新将用户带到可以进行购买的网页。另外,用于推荐的信息以及用户喜欢的车的信息将在app外部存储。
(图片说明:推荐系统的可视化界面截图)
总之,这个推荐应用使用了基于用户的协同过滤以及回归技术,以实现准确度地基于一些特定的汽车特征来为用户推荐汽车品牌和型号的功能。性能验证显示,推荐算法和回归模型都表现良好,成功组成了一个可靠的推荐平台。
注:本文翻译自《Recommending your car brand》。内容仅为作者观点,不代表DT财经立场。
题图视觉中国
关于DTNYCDSA
DTNYCDSA是DT财经与纽约数据科学学院合作专栏。纽约数据科学学院(NYC Data Science Academy)是由一批活跃在全球的数据科学、大数据专家和SupStat Inc.的成员共同组建的教育集团。
数据侠门派
数据侠Steven Jongerden毕业于代尔夫特理工大学,获得航空航天工程专业本科学位,政策分析和工程学硕士学位。他现在是荷兰凯捷管理顾问公司的一名数据科学咨询师。Steven在纽约数据科学学院进修,提升了自己机器学习和大数据分析的技能。
数据侠Huanghaotian Fu毕业于纽约大学,拥有数学和经济学硕士。目前在美国斯蒂文斯理工攻读金融分析硕士。他于2017年暑期完成纽约数据学院的培训。
加入数据侠
“数据侠计划”是由第一财经旗下DT财经发起的数据社群,包含数据侠专栏、数据侠实验室系列活动和数据侠联盟,旨在聚集大数据领域精英,共同挖掘数据价值。了解数据侠计划详情请回复“数据侠计划”,投稿、合作请联系。
时间序列分析(一) 如何判断序列是否平稳序列平稳不平稳,一般采用两种方法:
第一种:看图法
图是指时序图,例如(eviews画滴):
分析:什么样的图不平稳,先说下什么是平稳,平稳就是围绕着一个常数上下波动。
看看上面这个图,很明显的增长趋势,不平稳。
第二种:自相关系数和偏相关系数
还以上面的序列为例:用eviews得到自相关和偏相关图,Q统计量和伴随概率。
分析:判断平稳与否的话,用自相关图和偏相关图就可以了。
平稳的序列的自相关图和偏相关图不是拖尾就是截尾。截尾就是在某阶之后,系数都为 0 ,怎么理解呢,看上面偏相关的图,当阶数为 1 的时候,系数值还是很大, 0.914. 二阶长的时候突然就变成了 0.050. 后面的值都很小,认为是趋于 0 ,这种状况就是截尾。再就是拖尾,拖尾就是有一个衰减的趋势,但是不都为 0 。
自相关图既不是拖尾也不是截尾。以上的图的自相关是一个三角对称的形式,这种趋势是单调趋势的典型图形。
下面是通过自相关的其他功能
如果自相关是拖尾,偏相关截尾,则用 AR 算法
如果自相关截尾,偏相关拖尾,则用 MA 算法
如果自相关和偏相关都是拖尾,则用 ARMA 算法, ARIMA 是 ARMA 算法的扩展版,用法类似 。
不平稳,怎么办?
答案是差分
还是上面那个序列,两种方法都证明他是不靠谱的,不平稳的。确定不平稳后,依次进行1阶、2阶、3阶...差分,直到平稳位置。先来个一阶差分,上图。
从图上看,一阶差分的效果不错,看着是平稳的。