《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一1.2 原始数据-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一1.2 原始数据

简介:

本节书摘来自华章计算机《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一书中的第1章,第1.2节,作者:[美] 德博拉·诺兰(Deborah Nolan)  邓肯·坦普·朗(Duncan Temple Lang)  更多章节内容可以访问云栖社区“华章计算机”公众号查看。

###1.2 原始数据
在CRAWDAD(A Community Resource for Archiving Wireless Data At Dartmouth)[2]网站上有两个可用于开发IPS的相关数据集。一个是“离线”的参照数据集,它包含了一组用手持设备测量的信号强度数据,这些数据是在曼海姆大学某大楼的一层大厅里按1米间隔共166个点组成的网格上测出的。楼层平面为15m×36m,如图1-1所示。平面上的灰色圆点标示出具有离线测量值的位置,黑色方块标示出6个接入点。这些参照位置给出了大楼的信号强度的校准数据集,使用它们可建立一个关于手持设备位置的预测模型。当该手持设备的位置未知时,可以用该模型进行位置预测。
除了提供手持设备的坐标(x,y)之外,还提供设备的方向数据。信号强度按照45霸隽浚?、45、90等)在8个方向上分别加以记录。此外,关于数据的说明文档中指出,对6个接入点的每一种“位置-方向”组合,共记录了110个信号强度测量值。
image

图1-1 测试环境的楼层平面图。图中,用黑色方块标示6个固定的无线接入点,用灰色圆点标示收集有离线/训练数据的位置。在用黑色圆点标示的点上,记录在线数据的测量值,这些点是随机选择的。灰色圆点之间的空间距离为1m
除了离线数据之外,第二个数据记录集称为“在线”数据集,也用于建立预测位置的模型。在这些数据中,随机选择了60个位置和方向,测量了它们距每个接入点的110个信号值。在图1-1中,这些测试位置用黑色圆点标示。在离线数据集和在线数据集中,110个信号值中的某一些值并没有在数据集中记录下来,反而在实验单元附近的其他手持设备(如手机或笔记本电脑)的测量值却保存在某些离线数据记录中。
数据的说明文档[2]描述了数据文件的格式。我们自己可以用文本编辑器来查看文件。这两个文件(离线和在线)具有相同的基本格式,文件的开头如下所示:

注意,从第4行到随后显示的各行在文本中实际上是一行,为了更具可读性,这一行被格式化为多行。我们加了符号来指示行的连续。
文档说明数据的格式如下:

这里,关于测量单元的描述如表1-1所示。MAC(介质访问控制)变量指的是硬件设备的MAC地址,它是使得一台计算机、一个接入点或者一件设备的网卡可在网络中被识别出来的唯一标识符[5]。按照约定,标识符的形式写作mm:mm:mm:ss:ss:ss,这里mm和ss是两个十六进制位(0,1,…,9,a,b,c,d,e,f)。开始的3对数字组mm:mm:mm标识设备的制造商。后面的3对数字组(ss)标识具体的设备,对应其型号和唯一的设备号。
image

数据中的MACofResponse1...MACofResponseN表示:一个记录行由不固定个数的MAC地址上的测量值组成。就是说,这些记录不是等长的,它们形成了一个参差数组,每行的长度依赖于检测到的信号个数。例如,请看输入文件的另外一行(第2000行):

通过上面的数据我们可以看出:该记录有8个读数;MAC地址出现的次序与上述第一个记录不同;同一个接入点有两个读数(8a接入点);8个地址中有一个属于adhoc设备,因为按照表1-1,模式的数字代码指示该读数是属于adhoc设备(Mode值为1)还是属于接入点(Mode值为3)。再回头看第一个观测值记录,我们会注意到超过6个MAC地址具有模式3。这些“额外”的数据来自大楼的另外一层。
现在我们已经对输入文件的格式有了一定的认识,下面就可以确定如何将数据读入到方便进行分析的存储结构中。首先,我们要考虑如何用R来表达最终的结果数据[8]。有两种显而易见的合理选择。第一种是将输入文件中的每一行对应于数据框中的一行。对于这种情况,数据框中的变量分别是时间、mac-id、x、y、z(手持设备的位置)、方向以及各个MAC地址所对应的4个变量(用来表示采集到的信号,包括信号、信道、设备类型以及MAC地址)。由于原始观测记录包含不同个数的信号记录,因此我们的数据框需要有足够多的列以支持包含最多信号数量的记录。
第二种方法是先使用相同个数的初始变量来描述手持设备,即时间、MAC地址、位置和方向。然后,使用另外4个变量描述接收到的信号:接收到信号的设备的MAC地址、信号、信道和设备的类型。这样,每个接收到的信号对应于数据框中的一行。结果是,输入文件中的每一行记录转化成数据框的多行记录,其行数等于输入文件行中由“;”分割开的MAC地址的个数。例如,在数据框中,上面输入文件的第一条记录变成了11行记录,第2000条观测记录变成了8行记录。
第一种方法为自然的表达方式,能够更直接地对应于输入文件的格式,也避免了重复时间、位置等信息,看起来更省存储空间。但是,它带来的一个难题是我们必须提前确定需要多少列,更具体地说,从该设备上可以接收到多少个MAC地址。即使删除了adhoc列,仍然要求处理来自同一个MAC地址的不同次序的记录和多个测量值。这样就很可能需要进行两遍数据处理,才能够建立起该数据框:在第一遍确定唯一的MAC地址,在第二遍进行数据组织。尽管我们避免了重复存储一些信息,如时间戳,但是,对于没有从任何MAC地址记录到信号的那些观测结果,需要使用NA值来表示。如果有很多这样的值,则采用第二种方法实际上更省存储空间。第二种方法也简化了数据框的创建过程。
使用第二种方法,我们避免了对数据的两遍处理,只需一遍就能把文件读入数据框。并且,数据框这种数据结构允许我们对MAC地址使用group-by操作。因此,我们先使用第二种方法。以后,我们再介绍如何按第一种方法创建数据框。那时,我们不需回到原始文件,而是用已有的数据框来创建另一个数据框。
在确定如何将数据读进R时,另一个需要考虑的问题是,“注释”行是否只出现在文件的头部。我们可以搜索文件,找出这些以“#”开始的行。为此,我们需要用readLines()函数将整个文档读进R。

这样,离线文件中的每一行作为字符串读进R的字符向量txt之中。我们使用substr()函数定位以“#”开始的行或字符串,并对其个数进行求和:

接下来,我们执行length()函数:

确定出离线文件共有151 392行。按照文档说明,我们预期在文件中有146 080行的观测记录(166个位置×8个角度×110个读数)。两者之差是5312(151 392-146 080),正好是注释行的个数。
总之,可以遵循的常用经验是,要仔细检查关于文件格式的假定,而不是依赖于只查看文件开头的少数几行说明。
对原始数据的处理
现在,我们确定了数据在R中按照预期目标的表示形式,下面就可以编写代码从输入文件中抽取数据并处理成这种形式。由于输入文件中的数据不是以方形表格形式表示的,因此不能直接使用类似于read.table()的函数。不过,可以利用观测记录中的结构来处理文本行。例如,主要的数据元素是以分号分隔开的。下面我们看看如何用分号划分第4行,该行是文件中第一个非注释行。

在上面每个短字符串中,变量的“名字”和与其关联的值被“=”分隔开。在某些情况下,这个值包含用“,”分隔开的多个值,例如,"pos=0.0,0.0,0.0"由3个未命名的变量组成。
对于这个按照分号进行划分而产生的向量,可以进一步在“=”字符处划分出每个元素。再接着对这个结果在“,”字符处进行划分。处理如下:

最终将得到一个长长的字符向量,第一条数据记录中所有的变量名字和数据值都作为单独的“tokens”(标记)存在于该向量中。接着可以把它们整理成合适的形式。不过,我们可以做得更简单、更通用一些,由于strsplit()中的split参数可以是正则表达式,这样,在一个函数调用中,可以在任何几个字符上做划分。这意味着可以使用如下调用,在“;”“=”或者“,”字符处做划分:

在我们编写更多的代码来读取这些数据之前,不禁会问:read.table()是否也可使用正则表达式作为分隔符?如果可以,就可以用它来替代readLines()。遗憾的是,因为它读取常规文本文件的速度相当慢,因此不能用它替代。
基于strsplit()的结果,我们得到了第一行中的所有数据元素。tokens中前10个元素给出了手持设备的信息。

我们可以用如下语句从这些变量中提取数据值。

我们知道这些值对应于如下变量:时间、MAC地址、x、y、z以及方向。
下面我们来处理观测结果中记录的信号数据。它们是被划分的向量中余下的数据值。

我们可以将其看成是一个4列矩阵或一个数据框,所包含的列分别对应于MAC地址、信号、频道、设备类型。对这些行进行分解,利用分解后的值就可建立起一个矩阵。然后,我们将这些列与记录的前10个中的某些值绑定到一起。做法如下:

这样,将生成一个11行10列的矩阵,每一行对应一个MAC地址,并且有6列对于所有的MAC地址具有相同的值(如位置和方向)。

我们把这些代码放到一个函数中,这样,可以对输入文件中所有的行都重复执行该操作。即

让我们将该函数应用于输入文件的若干行:

注意,我们从文件的第4行开始,因为前3行是注释。执行结果是一个包含了17个矩阵的列表。用如下命令可以确定在每个点上有多少个信号被检测到:

至此,我们完成了较困难的部分。当然,我们更想把这些单个的矩阵转成一个统一的数据框。我们可以使用do.call()函数将这些矩阵堆积到一块。我们也可能倾向于写一个循环语句,将第二个矩阵级联到第一个矩阵,再将第三个矩阵级联到第二个矩阵,依次类推。但这处理起来非常慢(可以尝试一下!)。而利用do.call()函数能够简单而高效地实现矩阵的堆积。我们在调用do.call()时,只需指定将要调用的函数名和由各个参量构成的一个列表即可,而我们通常的做法是将这些参量分开传递给该函数。这样,我们只需简单地执行如下语句:

现在,我们尝试对整个数据集执行这个代码。首先,丢弃以注释字符“#”起始的行,再将其他的行传递给processLine()。

当我们运行这个代码时,会得到如下形式的6条警告消息:

一般来说,我们应对警告消息保持特别警觉。
虽然通过仔细查看输出结果,我们可以找到这些警告消息所对应的程序行。但是,如果能在警告消息出现时就立即查看,则更容易找到这些程序行。我们可以要求R系统在给出警告消息的同时抛出一个出错消息,这时可浏览调用栈,检查计算状态。为此,可以设定一个选项来处理错误,并设定另外一个选项将警告消息变更为出错消息:

下面,我们用这些新选项再次运行lapply()调用:

当第一个警告消息出现时,系统给出如下消息和调用栈。

我们选择3,对应于processLine()函数。我们可以向这个调用框发出R命令。例如,我们可以查看ls()函数有什么变量。再检查变量x,看到它的值如下:

对于这个值,我们注意到在这个位置上没有检测到任何信号。这个观测值属于异常情况,需要用processLine()函数加以处理。
我们可以修改processLine()函数,要么丢弃这种观测值,要么在数据框中增加一行,包含该手持设备信息,而对应的MAC地址、信号值、频道和设备类型都设为NA值。当一个观测值无助于建立定位系统时,我们就丢弃它。我们将该函数修改为,当tokens向量只有10个元素时,就返回NULL。修改后的函数如下:

我们再次运行更新后的processLine()函数,看看警告消息是否消失了。

的确不再收到警告消息。我们的数据框offline共有100多万个数据行:

该数据框由字符值变量组成。下一步是将这些值转换为适当的数据类型,例如,将信号强度转换为数值型,并按照需要做进一步的数据清洗。这是下一节的主题。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: