首页 技术 正文
技术 2022年11月14日
0 收藏 681 点赞 3,217 浏览 21167 个字

无论是现今的大数据还是企业内部的小数据,都存在一些普遍的问题,如数据格式不对需要转换,一个单元格内包含多个含义的内容,包含重复项等等,虽然我们也可以使用excel解决,但是excel天生有诸多限制,比如其为直接对数据进行操作,容易导致误操作;数据量大会处理缓慢;透视表功能太过简单;无法进行高级的数据分类分析。而OpenRefine很好的解决了以上问题,最重要的一点是它还是免费的!!

确认错误是保证数据质量的第一步,主要包括数据画像和数据清洗。

  • 数据画像 Olson定义为:使用统计方法发现数据的结构、内容、质量。换句话说,这是一种对你的数据进行画像,预发现包含的错误信息的方法
  • 数据清洗:用半自动化的方式改正画像过程中发现的错误,比如:删除缺失和重复值、行过滤透视、值聚类及转换、单元格拆分等等。

鉴于后续章节都需要保证数据已经画像清洗过。OpenRefine是一个数据转换工具(IDTS),其能够对数据进行可视化操作处理。它很像传统的excel软件,但其工作方式更像数据库,因其并不是处理单独的单元格,而是处理列和字段。这意味着OpenRefine对于增加新行内容表现不佳,但对于探索、清洗、整合数据却功能强大。

第一章 基础

1.1、创建新项目

OpenRefine使用默认浏览器中打开,程序在本地运行,点击下图中的“选择文件”按钮,导入演示实例中的phm-collection.tsv文件,然后点击下一步。

OpenRefine

当数据上传之后,会出现导入数据的预览和格式需求。

OpenRefine

默认情况下,第一行数据会被解析为列名称。OpenRefine同时也会猜测单元格类型,给其赋予整数、日期、网址等等,这在你后续整理排列数据的时候十分有用(比如如果你将单元格设为文本格式,那么10就会排在2前面)

另一个选项是“引号在原数据中用来分隔列”选择项,如果选中,则原数据中引号用来分隔列,否则就需要去掉勾选框以使得OpenRefine能够正确读取。在Powerhouse数据集中,引号是用来表明物体名称和说明信息,所以此情况下,引号没有分隔列的意思:所以这里我们需要去掉勾选。剩下的选项在某些情况下需要设置,试着勾选或者去掉勾选来看看如何影响数据。另外,请确保编码正确,以使得显示正确。当所有都设置好后,点击创建项目来加载数据。

1.2、探索数据

数据加载之后的界面如下:

OpenRefine

上图中标明1~4的四个区域,我们按照顺序介绍:

1、总行数:如果你没有忘记去掉“引号有意义”选项(参照要点2-创建一个新项目),那么你会看到Powerhouse文件包含75814行数据。当数据按照某个参数进行过筛选,这里的显示会变成类似于找到123匹配行(总共75814行)。

2、显示选项:试着点击下,将行变成记录来查看区别,事实上变化并不大,只不过该区域显示为75814条记录而已。行数量一般情况下等于记录数量,但在后续情况中还是不一样的。该区域可以让你选择按5、10、25、50每页显示,并且你也可以在这里跳转页。

3、列名称及菜单:你会发现数据加载后的第一行被解析为列名称,在Powerhouse数据集中,列包含Record ID, Object Title, Registration Number等等(如果你在创建时去掉了“将第一行解析为列名称”的勾选,那么列名称区域会显示为Column1、Column2等等)

4、单元格内容:此处显示实际单元格数据

在开始剖析清理数据前,十分重要的一点是确保OpenRefine较好的载入显示了数据:查看列名称被解析正确(数据显示较宽时请使用水平滑动条)、单元格类型是否正确等等。将行显示项改为每页显示50条以查看数据不明显矛盾(理想情况下,你应该在创建项目前的预览界面时处理这些工作)。当你已经熟悉了操作界面,你就可以继续下一步了。

1.3、操纵列

操纵列可以对列进行隐藏和展开、按需要转换、以及重命名和删除等操作。

列是OpenRefine中的基本元素:其是具有同一属性的成千上万的值的集合,可以按照很多方法查看处理。

1.3.1 隐藏和展开列

如果要隐藏一列或几列以方便观察操作,那么点击列下拉菜单,选择View,有四个可选项:

OpenRefine

以下是对Categories点击了收起所有其他列的效果:

OpenRefine

1.3.2 移动列

有时候改变原数据中列的顺序十分重要。比如,为了将两列放在一起比较。选择需要处理的一列然后在列菜单中选择“编辑列” 。子菜单中会出现如下选项,在最后的四个选项跟别是对列进行移动。

OpenRefine

如果对所有列操作,可以使用第一列名称为“全部”的列。这一列可以使你同时操作多列。“视图”菜单可以让你快速的隐藏和展开列。选择“编辑列”中的“重排/移动列”可以通过通过拖动重新对列进行排列,还可以将将列拖动到右侧来去除该列,如下图所示:

OpenRefine

OpenRefine

1.4、使用项目操作历史

OpenRefine一个特别有用的功能是可以在项目创建后保存所有的操作步骤。这也就意味着你不需要害怕做数据变换尝试:你可以随意按照自己的想法变换数据,因为一旦你发觉做错了(即使是几个月前做的),你也可以撤销该操作以恢复数据。

OpenRefine

为了恢复历史数据,单击你想保留的最近一个步骤操作。比如,为了取消上图第2步及以后的操作,可以单击第1步使其高亮显示,这样第2步之后就会变灰。这意味着选中项后的操作都将取消。如果点击第0步,那么所有操作都将取消。

请注意,撤销某几步操作后再做出新的操作的话,原来的后续操作内容会丢失.比如,如果你从第2步回退到第1步,然后对Description 列执行左移操作,那么会显示第2步操作2. Move column description to position 1 ,而先前的灰色操作项(第2步)会丢失:因为我们不能在同时拥有两种互相矛盾的操作历史记录。记得多尝试下上面的步骤,省的以后带来严重的困扰。

记住,只有对数据有实际影响的操作才会出现在项目历史操作表中。数据透视比如:交换行列视角、在一页中改变显示数目、隐藏或展开列并不改变原来数据,所以也就不会出现在操作历史表中。以上操作也就无法随着项目信息传递:当你重新打开一个项目,无论原来是否隐藏,这时所有的列默认都是展开的,但是对于重命名列和删除列这类操作会出现在操作历史表中。在第二章,分析和修改数据中,我们会看到还有一些类型的操作会存在在操作历史表中:比如单元格和列的变换、但是过滤和透视操作则不会。

操作历史也可以以JSON格式导出,可以点击“提取”按钮进行操作。

1.5、导出项目

虽然可能已经移动、重命名或者删除了列,但其实原始数据并没有被修改。不像有些电子表格软件直接将改动写进文件,OpenRefine只是对数据文件的一份拷贝进行操作,在界面右上角点击Export按钮进行操作:

OpenRefine

大部分弹出的选项能够让你将数据导出为常用格式,比如csv、tsv、excel和open document格式、还有不常用的RDF格式。让我们往下看:

  • 导出项目: 这个选项能让你导出OpenRefine格式的压缩包,你可以用来和其他人共享或者仅仅用来备份。
  • HTML表格: 这个选项可以让你方便的将文件发布到互联网。
  • Triple loader 和MQLWrite: 这个选项有一些扩展功能,能够让你将数据转换成支持Freeebase规则的样式(参照附录:正则表达式和GREL)
  • 自定义表格导出器:可能这是最吸引你的部分。OpenRefine可以让你对你的数据导出有更精细的控制,比如对列进行选择和排序、忽略空白行、正确选择日期格式以得到更有效的数据(参见附录:正则表达式和GREL)。如下图所示:

OpenRefine

1.6、小结

到这里,可以初步了解了OpenRefine,一种新的具有数据画像、清洗、转换等等功能的工具,并且已经了解如何在创建新项目中导入数据和如何在完成操作后导出数据以及行、列的运行机制。

第二章 分析和修改数据

2.1 数据排序

排过序的值更加容易理解和分析,以Record ID 列为例进行排序,选择列菜单中的“排序” ,将弹出如下窗口:

OpenRefine

OpenRefine

单元格值可以按照文本(区别大小写或者不区别)、数字、日期、布尔值排序,另外还可以对错误值和空值指定排序顺序。比如错误值可以排在最前面(这样容易发现问题),空值排在最后(因为空值一般没有意义),而有效值居中。

对Record ID列通过按数字升序排列为例,我们就会获得一列以7、9、14等等标识的列,而打开时是以267220、346260、 267098标识顺序的,下图对排序前后的情况做了比对:

OpenRefine

如果按照Text: 文本排序的话,会得到以100、1001、10019开始的一列。同时应该关注的一点是:排序并不会被记录在项目操作历史中。你可以在屏幕左侧顶部的“撤销/重做”页中确认下。因为排序并不会改变数据,其仅仅是改变了显示方式,比如行列互换、隐藏不想显示的列(就像excel的排序过滤功能)。所以每次对某列进行排序,你就会面对三种抉择:取消排序回到原来状态、暂时保持、永久改变,但是你可以在屏幕顶部的快捷菜单Sort来做到:

OpenRefine

Sort菜单可以让你移除排序操作或者永久对行进行排序。如果想将排序后的结果再进行后续操作,一定要将排序结果永久保存,比如对于空白单元格或者填充单元格。

2.2 数据透视

数据透视并不改变数据,但是可以让你获得数据集的有用信息。你可以把数据透视看作是多方面查看数据的方法,就像从不同的角度观察宝石一样。我们将学习如何按照要求或者数据具体的值来透视数据:对字符串进行文本透视、对数字和日期进行数字透视、几个预定义的透视功能、最后还有标星和标旗功能。OpenRefine的强大之处也在于这些透视功能的组合使用。

2.2.1 文本透视

如果你的数据集中包含城市或者国家名称的列,而你想大致了解下这个字段都有些什么值和这些值的统计次数有多少,那么就可以用文本透视。当然,只有该列中的类别总数不是特别大的时候文本透视才有用,因为文本透视并不是为了列出所有的信息,全部列出并没有多大意义,同时透视结果也不会出现相同的两个类别(除非有重复项,我们将在下一点中说明)

让我们试试看,点击Categories列菜单,选择“归类”|“文本归类” ,结果会出现在屏幕左侧的“归类/过滤器”页中。可惜的是,OpenRefine提醒我们总共有14,805个分类,已经超过了我们的电脑显示内存。事实上,透视不能超过2000个分类。

OpenRefine
这里的提示窗口的出现也就意味着分类数太多了,需要减少。其实OpenRefine在超出时完全有办法提高限制数,但实际情况下往往我们想减少限制数的,太多的分类对于我们毫无意义。如果想这么做,可以访问系统参数:http://127.0.0.1:3333/preferences ,编辑ui.browsing.listFacet.limit,然后调低到想要的值。如果想恢复默认值2000,直接删掉这个值即可。

点击下面的“按归类中量来归类”链接,将打开二级透视界面。新的透视也是对Categories的透视,这里可以让你对透视显示范围进行限定。当第一次打开的时候,OpenRefine会显示所有分类,无论分类数量只有1次还是有几千次。因为有太多的分类会被显示,所以一般开始的时候只显示数量较多的分类就比较有意义。

拖动左边的滑块从最小值0到1000,文本透视情况将自动更新,现在将只显示大于1000数量的Categories分类。这里只有7个,这就比先前大大减少了。为了观察方便,可以选择对“数量”(计数项)排序(降序)来替代按字母排序。下图显示你得到的透视图:

OpenRefine

如果你想导出最多的7个分类名称,可以点击上图中的7 choices链接,会得到一个TSV格式的内容,你可以复制粘帖到你喜欢的文本编辑器或者电子表格软件中。

可能你会觉得奇怪,比如Photographic prints|Photographs 并不是一个分类名称,其实包含两个分类,被“|”分开了。这就是为什么我们第一次透视会出现这么多的分类。Glass plate negatives|Gelatin dry plate negatives|Photographs, Glass plate negatives|Photographs和Glass plate negatives被区分为不同的分类,但是其实他们都属于一个分类。这就是多义单元格的困扰,这里将在第三章:高级数据操作学习中的按钮Cluster进行介绍。

让我们在对数值列进行透视前来看看另一个文本透视操作。Height虽然并不是特别明显,但是我们也可以看到这个列并不仅仅包括数值,比如还有一些带单位的数字990mm,这意味着我们不能使用数值透视(最起码不能直接用),但是我们可以试试文本透视,希望能够看出端倪。我们在对Hight列进行透视操作(“归类”|“文本归类”)时,我们发现只有1313个分类,低于2000限制数,用“数量”排序,我们发现164mm的数量有1368,而第二位的215mm只有400,如下图所示:

OpenRefine

在上图列表底部,我们可以看到Hight列中空值有45501,也就是说这些值缺失。

2.2.2 数字透视

如果在项目导入时勾选“将单元格的文本解析为数字、日期等”,那么数字也可以通过绿色来快速识别。本例中Record ID就是个很好的例子,通过对其透视可以看出ID的分布,并且可以看出是否每条记录都有一个ID。点击Record ID列菜单,选择“归类/数值归类”,然后看看左侧“归类/过滤器返回一个不同分类数量的列表,而数字透视则是某个数值范围的分布,就像我们通过频数来透视一样。

OpenRefine

我们可以看到Record ID的值域从0到510,000,在270,000至280,000之间有个小缺口,在410,000至500,000有个大缺口。通过滑动滑块,我们可以看出有533行的值大于500,000。

在图的下方,我们可以看到值类型被分成四种:数值型, Non-numeric,Blank和 Errors。这是因为一些错误所导致的。在我们操作前可以看到Errors(错误值)为0,blanks(空值)数也是0,说明每一行都被分配了一个ID,有三行的值为Non-numeric(非数字),不能用作ID。让我们只勾选Non-numeric来看看情况。现在右侧我们能够看到这三行的内容了。这三行不光ID缺失,其他很多列也是空白,除了persistent link和license information列,但是这些信息也是自动产生的。这几行并没有什么实际意义,所以我们可以删除(本章最后内容会介绍为什么会产生)

但是这里有个问题,如果这三行ID确实为空,那么为什么会被分类到Non-numeric,而不是分类到Blank?其实是空格导致的,我们可以通过编辑这几个单元格来验证。我们可以将鼠标移到单元格上,会出现edit标记,点击进入你就会看到包含一个单独的空格,如下图所示:

OpenRefine

为了改正这个错误,可以单击Backspace或Delete键删除这个空格,然后点击“应用到所有相同单元格”按钮或者按Ctrl_Enter。OpenRefne 会弹出一个黄框提示你有多个Record ID 列值被修改。最后,左侧的透视图会自动刷新,有问题的三行也会归到Black分类。

2.2.3 时间轴透视

时间轴透视要求数据为日期格式,所以类似17/10/1890 的文本字符串需要改为日期格式,17/10/1890 会被转成1890-10-17T00:00:00Z ,其中这些0标识一天中的小时、分钟、秒。你可以用Production Date 列练手,但是请注意,真实的时间很少是确定的,比如仅仅包含年份的1984,或者一个时间范围如2006到2007.我们可以对某列做如下操作:编辑单元格|常用转换|日期化。

OpenRefine
下图显示了将Production Date 列转换成日期后的时间轴透视图:

OpenRefine

2.2.4 散点图(略)

2.2.5 定制透视

我们现在已经学习了两种主要的透视方法-文本透视和数字透视。openrefine也提供一些预定义透视选项,其可以给大多数用户提供有用的透视功能。让我们先看一下“自定义归类”子菜单的内容,我们可以在列菜单“归类”菜单中找到。

OpenRefine

  • 按字归类(单词透视)列出了字段中所有不同的单词(适用于英文),一个单词被定义为两个空格之间的字符。这在你想对数据小子集进行分析时十分有用,因为大数据的话统计频数会很快增加到很大,这很容易导致软件奔溃。比如对列Description进行单词透视,会显示212,751个不同的单词。
  • 复数归类(重复项透视):检测重复项
  • 数据对数归类 (数字对数透视):对数字的对数值进行透视,这在数字符合幂分布时特别有用。
  • 约为1的数字对数归类(1阶数字对数透视)也一样,只不过其不能应用于小于1的数字。
  • 文本长度归类(文本长度透视)针对的是字符串中的字符个数,以Object Title列为例,我们可以看到该列中的内容字符个数从0到260,你可以看到有92个标题内容中内容小于10个字符数(无内容的占大多数,这毫无意义),你还可以看到2007个标题内容超过250个字符数(一般情况下一定需要精简了)对descriptions列进行文本长度透视也十分有趣,但是我们看到这个分布实在太广了点(从0到4100,虽然85%数量的内容小于500个字符数),这导致我们很难了解其规律。这时候我们可以使用文本长度对数透视,如下所示:

OpenRefine

  • Unicode 字符归类(Unicode字符集透视)并不计算字符串的长度,而是列出了所有字符串中使用的字符UNICODE码数。在Object Title列尝试下;大部分英语字符UNICODE码数小于128,一些源于欧洲语言的古文UNICODE码数会达到256,阿拉伯文或中文甚至更大。如果你发觉字符UNICODE码数特别大,可能是OpenRefine没有正确的识别出编码。碰到这种情况,可以新建一个项目然后选择手工指定正确的编码方式。
  • 按错误归类/按空白归类:按照空值来透视在了解字段填充分布时十分有用,一般常用来与另一个透视结果作比较。我们看到Marks列开头几行就为空,所以我们通过点击”按空白归类“来了解该列,透视结果提示我们18,986个为FALSE(也就是非空),86,846个位TRUE(也就是空值)。换句话说,只有四分之一的部分有内容。还有明显的列是Weight(在屏幕右边,使用水平滑动条滑动可见),只有179个有内容,而99.998%的内容为空。对Object titles列进行空值透视发现,有118行是无效行,它们都有一个RECORD ID跟着一个链接和无效的信息,但是其他列却都是空值,所以它们并不代表一个有效记录。

2.2.6 对标星和标旗进行透视

要标星或者标旗,只需要在对应的符号上点击即可。大都数情况下,通过透视过程实现同时标注多行。比如,可以通过对Registration Number列执行“归类”| “自定义归类”| “按空白归类”来检测出空值,然后点击true来选出118行空行。然后点击“全部”列菜单中“编辑行”|“加星标”,这样便可以对这118行进行加星标。

OpenRefine

现在假设你想显示要么diameter字段有内容或者weight字段有内容的行。如果你对这两列都做了空值透视,然后分两次点击结果是false的内容,你将得到29行匹配,但其实这29行指的是diameter字段有内容而且weight字段也有内容,这和我们的目的不符,我了按要求取到数据,解决方法是分两步:先对diameter列进行空值透视,得到2106行为false(也就是diameter内容存在),然后使用All| Edit rows| Star rows标星,清除透视并且对weight列进行空值透视,得到179行(你会注意到只有150行被标星,因为29行已经被标星,其weight和diameter都有内容而已)。再次清除透视,然后选择All| Facet| Facet by star获得数据,当然使用旗帜功能也可以。

2.3 重复检测

重复值是数据集中出现两次或更多次的恼人数据。重复数据不仅浪费存储空间,并且会导致干扰。重复项透视就是一种能够检测重复的简单办法。但是其也有限制性,比如其只能对字符串进行重复检测,最起码不能直接对非字符串进行操作,我们无法对Record ID列进行重复项透视。

这里最好的选择就是对registration numbers列(内部标识)进行重复项透视:“归类”|“自定义归类”|“复数归类”,281行被标注为重复项,点击左侧中的true显示这批数据。现在鼠标滚动下查看这些重复项。我们发现一个问题:重复项中包含空白行,这些实际上确实完全一样,但是和有效行的重复是完全不同的。

为了剔除这118行空白行,我们需要再对Registration Number列作一次空值透视:“归类”|“自定义归类”|“按照空白归类”.点击false保留163行真正的重复数据,我们发现结果自动刷新了。

最后,再增加一个透视,这次是一个简单文本透视,列出数据集中有多个相同registration numbers的项,按照计数项排序,我们看到79项中,77项确实是严格的重复项(重复2次),(2008/37/1)出现了3次,(86/1147-3)甚至出现了6次,如下图所示:

OpenRefine

现在让我们回到RECORD ID列的讨论来,因为重复项透视不能用于整数,所以我们将采取一个迂回的办法来检测该列。首先,我们对ID列进行排序,其中参数项选择“数字”和“从小到大”。排序只是一种视觉的改变,所以为了让数据永久改成排序状态,我们需要选择Sort菜单(就在Show: 5 10 25 50 rows右边),点击“固定行顺序”。如果你忘记做了这一步,后续的操作其实会忽略排序这个动作,从而导致不可知的结果。

现在我们已经得到了排序后的数据,ID重复项一定是在一起的。所以我们选择进行如下操作:“编辑单元格”|“相同空格填充”,重复项中的ID会被空白替代(所有重复项中第一个保留,后续的空白填充)。然后进行一次空值透视:Record ID | Facet|Customized facets| Facet by blank,我们将得到86个冗余行(如果你删除过空白行,也可能是84),其中二次重复项会出现1个,三次重复项会出现2个,六次重复项会出现5个。这86个重复项就是保留1项后剩余的需要删除的冗余项。

2.4 文本过滤

假如你想找出Object Title列中所有和美国相关的所有标题。选择Object Title| “文本过滤”,我们将在左侧看到一个对话框,就在上节中透视对话框相同的位置。现在输入USA。OpenRefne 提示匹配到1,866行,勾选case sensitive (区别大小写),那么比如karakusa和Jerusalem之类的就会被排除,这样我们降低到1,737行。

OpenRefine

这样简单的文本过滤并没有考虑到拼写方式,比如遗漏了U.S.A. (201 个匹配), U S A (29个匹配)或 U.S.A (22个匹配)。这时候就需要用到正则表达式了,正则表达式十分强大,但是需要你对这些表达式中的奇怪符号有基本的认识,这样才能发挥其功效。比如,表达式\bU.?S.?A\b(文本过滤时需要勾选regular expression)将匹配上面所有需要包括的内容,并且把干扰项排除,最后将返回1,978项匹配。

文本过滤的另一个应用是检测分隔符的使用,在Categories列种,管道符”|”被用来分隔目录,让我们对Categories列应用一个文本过滤,过滤符为“|”,OpenRefine显示匹配到了71105行,这说明大部分内容里面含有至少两个目录(因为单个目录不需要管道符分隔)。

2.5 简单单元格转换

在我们检测数字重复值的时候我们已经用到过了Blank down菜单。另外的转换功能比如分割合并单元格、聚类、计算值相对来说较复杂,所以我们将在下一章学习。其他的转换功能比较简单,所以我们将先学习“编辑单元格”|“常用转换”子菜单下的功能,如下图所示:

OpenRefine

  • 移除首尾空白:对数据进行删除多余首尾空格操作是提升数据质量的很好的开始,特别是唯一标识符也不应该有空格,只能对字符串操作,不能对数字操作。
  • 收起连续空白
  • 反转义HTML字符:如果你碰到一些值是以&开始并且以;结束的话,试用该功能,这样内容就能够被正确解析。
  • 大小写转换:我们能够将文本字符串转换成全部小写、全部大写或者首字母大写。要注意整数是否被转换成了字符串(因为数字被认为没有被大写)。你可以试着对registration numbers列先做一次数字透视,然后进行大小写转换来看看这些数字的变化,程序提示你没有数字,也就是说这次大小写转换将数字转成了字符串。

OpenRefine

  • 清空单元格:它将删除该列所有的内容。

我们已经介绍完了常用的转换方式,但是请注意这些只是转换功能的冰山一角,转换的方法不可计数,这你会在第三章:高级数据操作和附录:正则表达式和GREL体会到。

2.6 删除匹配的行

在实际情况中,这意味着那些有问题的行需要从数据集中删除,因为它们的存在是对数据质量的损害。在删除行前,请确保你已经做过了一个透视或者过滤,不然你可能会误将所有数据删除,另外请确保OpenRefine是以“行”rows显示而不是以“记录”records显示。

我们将首先删除RECORD ID中没有内容的行。首先对Record ID列进行操作:“归类”|“数值归类”,在左侧弹出面板中去掉“数值型” 勾选,这样我们就只剩下Non-numeric数据,这里我们有3条。现在使用“全部”|“编辑行”|“移除所有匹配的行”删除这个行。

OpenRefine

这样数据集减少了3行,,数据质量也提高了一点。现在清除透视后我们发现数据行数量下降到了75,811行。

下一步,我们将处理数据集中registration number列有问题的行。该列没有空格(你可以对Registration Number列应用一个简单的文本过滤,输入一个空格字符,我们发现没有匹配行)。所以我们对该列进行空值透视:“归类”|“自定义归类”|“按照空白归类”。选择为true的值,得到115条匹配行。这些就是空行,我们可以用Remove all matching rows删除。

现在我们将处理重复行。重复行就稍显麻烦点,但我们还是需要删除它们的,可能你需要回顾下检测重复项,对Registration Number列执行:“归类”|“自定义归类”|“复数归类”。选择true得到163行匹配行。问题是,如果你直接删除这些行,那么不光重复项会被删除,那个唯一的值同时也会被删除。换句话说,如果某行出现了两次,那么删除匹配行就会把两条都删除而不是仅仅删除一条。不过即使你误删除了,你也可以通过项目历史恢复。

所以我们需要做到既去除多余重复项,同时还能够保留一项。我们可以这么做:对Registration Number进行排序,选择text和a-z选项(case sensitive不必勾选,因为该列只有大写),然后选择Sort| Reorder rows permanently来固定排序。最后,使用Registration Number | “编辑单元格”|“相同空白填充”将多余的重复项使用空白填充。最后有84个单元格被修改。

如果你的重复项透视界面还打开着,那么你会注意到重复项刷新为84行。这是因为去重后的值(163条中的一部分)被摘出了,这时候它们已经不再是重复项了,所以在重复项透视中被识别为false。而真正的重复项则被填充成了空白,它们具有同一个值:空值。这就是出现84项重复的原因。现在你可以将这些项删除,并且能够保留下原值。

正常情况下,你现在将还有75,612行数据。我们还可以进一步处理,但是你可能已经知道如何去做了,这些就留给你们去实验。最后看下Undo / Redo页中项目历史究竟我们做了哪几步。

OpenRefine

2.7 小结

在本章中,我们学习了如何使用OpenRefine来分析和修复数据的基本操作,这是数据分析和清理的最基本技能。

分析数据包括排序和各类透视功能,还包括文本过滤和检重。

修复数据步骤则包括排序、单元格转换、删除。

第三章 高级数据操作

3.1 对多值单元格的处理

在很多表格数据中有一个普遍的问题:如果一个单元格中有多个值怎么办?举个例子,如果有一张包含名字、地址、电话号码的客户信息表格。录入人员正在对这张表格进行信息录入,当其发现有一个名字为Mr.Thompson的人有两个地址信息,并且每个地址信息对应一个电话号码,一般情况下录入人员会选择下面三种可能的操作:

  • 只增加一个地址信息:这是最简单的做法,这减少了一般的录入工时。但是,这也意味着丢失了一般的信息,所以表格信息完整性被降低了。
  • 增加两行:虽然现在表格信息是完整了,但是数据却出现了冗余。数据冗余也不好,因为这很容易导致错误:这两行可能会被认为是两个不同的都叫Mr.Thompson的人的信息,但如果这是Mr.Thompson不同时间所留下的信息,就容易导致错误。另外,因为这两行没有什么联系,如果其中一行信息被更新了,另一行并不能自动更新。
  • 在一行中添加所有信息:这种情况下,两个地址信息和两个电话信息都被添加到对应字段中一个单元格内。但是对于原先字段的定义来说,我们这样操作可以说是信息超载了。当然这样操作对于信息来说是符合完整和非冗余,但是也会有个问题。对于我们人类来说我们可以毫不费力的识别出这些信息含义,但是计算机却做不到。想象下一个写信的人在信封上写了两个不同的地址;或者是一台自动拨号机,其是通过将一个单元格的所有内容作为一个电话号码来进行拨出。上面两种情况都会产生错误。所以字段确实丢失了语义上的精确性。

虽然我们知道我们有很多种技术方法可以解决多值单元格问题,比如表格关联。但是,如果数据模型你不能控制,那么你也就只能选择上面三种中的一种

幸运的是,OpenRefine可以做到多值单元格的识别。当然因为OpenRefine是一种自动化软件,所以其需要在操作前指定某个字段为multi-valued多值字段。在Powerhouse Museum数据集中,Categories列就包含多值单元格,因为其单元格内容可以属于不同的分类。在我们进行操作前,我们必须告诉OpenRefine这个字段是有点不同的。

假如我们想了解Categories列中究竟有多少不同的分类,并且哪个分类数量最多。选择Categories下拉菜单:“编辑单元格”|“分离多值单元格”,这里我们使用了“|”作为分隔符,如下图所示:

OpenRefine

确定之后可以看到OpenRefine已经分割好了单元格值,并且对Categories的透视界面也刷新了,显示了单独的分类。默认情况下是按照字母顺序显示的,如果我们按照频数显示的话我们能够获得更加有用的信息。我们可以将Sort by 选项从name改为count。这样我们就能够发现出现数最多的分类。

下面还需要做的是那些还没有变为单值的分类,我们需要将分类名称修改下,这样所有的行都会更新。比如,修改分类名Clothing and Dress,在透视界面中移动到该分类名上方,点击edit,如下图所示:

OpenRefine

输入一个新的名称比如Clothing,然后点击Apply。OpenRefine 就会把所有Clothing and Dress名称修改为Clothing。并且透视界面也立即刷新了。

一旦你已经将分开的值编辑好了,那么你可以把他们重新组合在一起。点击Categories列的菜单:“编辑单元格”|“合并多值单元格”,然后输入你想要得分隔符。这次的分隔符并不一定要和原来的相同,有很多字符可以使用。比如,你可以使用逗号后面跟一个空格来做分隔符。

3.2 行模式和记录模式的转换

现在让我们看下OpenRefine是如何处理多值单元格的。当我们按照上一点的操作步骤对一个列进行了分割后,我们发现OpenRefine做了两件事情。一方面,多值内容中的第一部分被替换放回原来的位置,另一方面,剩下的值被放到下一空行中对应的位置。举个例子,如下图所示,你可以看到ID7-ID9的记录基本是空行,只有Categories对应的单元格有内容,只有第一行(ID6)中其他单元格内有内容(ID6):

OpenRefine

row是指数据集中的一行。

Record包括一个主体中的所有行。第一行所有单元格非空,标识一条记录;后续行中相同内容为空,表示这些行隶属于同一条记录

虽然这种处理方式避免了信息的重复和错误,但是也使得比较难分辨隶属于哪个主体。比如,如果我们对Categories列进行了文本透视(参照前一点),我们可以点击每个类别名称来看究竟有哪些行属于这个分类。但是,如果我们这么做的话,我们会发现许多空行:

OpenRefine

产生上图结果的原因是:OpenRefine其实确实显示了所有分类值为Numismatics的行,这些行中包括那些Numismatics不是首个分类名称的行。但是对于某个主体中的其他行却并没有包括。这在我们关注某个主体中的所有行时就会产生问题。比如,我们可能想对所有类别为Numismatics的行进行标星操作,从而想对其进行后续操作时就会产生问题,我们可以试着这么做然后看看发生什么情况。

选中文本透视视图中的Numismatics,点击ALL下拉菜单中Edit rows | Star rows,然后点击文本透视视图中的reset来看看发生什么情况。我们发现只有值为Numismatics的行被标星了,而隶属于主体的其他行却并没有被标星。很显然我们丢失了信息。所以,让我们通过Undo / Redo标签页撤销标星操作

OpenRefine可以让我们将隶属于同一个主体的所有行集合成一个单独的record(记录)。这样做的话,可以让我们在分割多值单元格的同时确保这些行还能够被认为是一个整体。我们可以在“展示方式”中将“行” 改成“记录”。你立即会发现行中颜色的改变。其会从以每个row进行颜色区隔变为以每个record进行颜色区隔。

如果我们在records模式中,在Categories透视图中选中Numismatics,我们可以发现包含Numismatics的所有主体都被选中了。如果我们通过ALL下的“编辑行”|“加星标”进行标星,我们发现所有包含Numismatics的主体都被标星了。

OpenRefine

以上说明,在records模式下,操作对整条记录有效,记录起码是一行以上。总结下,我们可以这么说,rows模式只是各个独立的行,而records模式则是一个整体,可以包含数行。

如果你想匹配既在目录名称为Numismatics中又在目录名称Medals中的记录该怎么办呢?为了做到这点,首先请确保我们在records模式下,然后我们对Categories进行透视,首先我们选择Numismatics,然后再做一次透视,这次选择Medals,这样我们就获取到了我们想要的记录。

现在如果切换回rows模式会怎么样呢?突然,没有记录获得匹配。你起初可能感到疑惑,但其实很正常:没有一行是同时既等于Numismatics又等于Medals的,每一行最多只有这两项中的一项。因此,多重选择必须在records模式下。

另外请注意,其他章节请切换到rows模式,否则可能出错。如果出现不可预料的错误,请先检查下模式是否正确。这能减少很多麻烦。

3.3 相似单元格聚类

如果你在分割多值单元格后对分类进行了分析,你会发现同样的分类并不一定有相同的拼写。比如,Agricultural Equipment 和 Agricultural equipment(大小写不同),Costumes 和 Costume(单复数区别)等等。好消息是这类问题可以借助OpenRefine自动处理。

找到拼写有微小区别的内容的过程叫做clustering(聚类),在你分割好多值单元格后,你可以点击Categories列下拉菜单:“编辑单元格”|“簇集并编辑”,OpenRefine会打开一个对话框,这里可以选择不同的聚类方法,不同的方法都提供了很多相似的功能。打开时默认参数为“关键词碰接”key collision和“指纹分类算法”fingerprint选中。OpenRefine会完成对Categories列的聚类算法计算,最后其会列出所有的聚类项,每一项都包含拼写有微小差别的行,并且会提供一个整个聚会项的建议值,如下图所示:

OpenRefine
请注意OpenRefine并不能完全自动的合并聚类值。事实上,OpenRefine需要你确认是否这些值真的指向同一个内容。这个措施可以名称相似但意义不同的内容被合并。

  • “簇大小”:代表某个名称有多少种不同的拼写方式被找到。
  • “行数”:代表有多少行数据被找到。
  • “簇中值”:指明不同的拼写方式并且其数量,另外,这些拼写方式可以被点击,所以你可以确定那种是正确的,如果你将鼠标移动到某个拼写上方,会出现“查看这个簇”链接,你可以点击查看所有这类型的数据。
  • 是否合并?:包含一个勾选框,如果你选中它,那么所有的值将会修改为“新的格子值”中的内容,但你需要点击“合并选中”按钮确认。当然,你也可以手工指定一个值,如果自动出现的值并不符合你的要求。

这里强烈建议要仔细的检查列表,不然会把不同的值聚类到一起。在本例中,所有的建议聚类项都是正确的,可以直接点击“全选”按钮,然后点击“合并选中&重新簇集”按钮,这个操作可以让我们将所有聚类项进行聚类操作并且不会关闭界面,这样我们还可以试试其它聚类算法。

OpenRefine立即用相同的算法进行了聚类,结果会显示没有需要聚类项了。让我们尝试下换种聚类算法,可能发现OpenRefine又发现了几个新的聚类,但是请记住我们要求仔细检查所有的列表,你能发现错误吗?仔细看看下图:

OpenRefine

确实,这种聚类算法将Shirts和T-shirts 认为是相同的,但是这可能并不对,所以,要么手工选择所有正确的建议,要么不要勾选不正确的项。

OpenRefine提供两种不同的聚类模式,“关键词碰撞”key collision和“就近原则”nearest neighbor,这两种模式原理不同。

  • 关键词碰撞key collision,我们使用键函数来影射某个键值。相同的聚类有相同的键值。比如,如果我们有一个移除空格功能的键函数,那么A B C, AB C,和 ABC就会有相同的键值:ABC。事实上,键函数在构建上更加复杂和高效。
  • 就近原则Nearest neighbor使用的是一种技术:值与值之间使用来distance function衡量。比如,如果我们将每一次修改成为一个变化,那么Boot 和 Bots变化数是2:一次增加和一次修改。对于OpenRefine来说,其使用的distance function称为levenshtein

在实际应用中,很难确定究竟哪种模式和方法组合最好。因此,最好的方法是尝试不同的组合,每次都需要小心的确认聚类项是否真的可以合并。OpenRefine能够帮助我们进行有效组合:比如,先尝试key collision ,然后 nearest neighbor。

3.4 单元格值替换

在第二章:分析和修改数据中,我们学习到OpenRefine可以自动修改一列的单元格内容,比如去除多余空格。上一点中,我们学习到聚类是另一种修改列单元格内容的方法。然而,以上方法都仅仅是单元格值转换通用方法的一部分。你可以通过不同的稍显复杂的方式修改单元格值。虽然这看起来像是EXCEL公式,但是你会惊讶于其功能的强大。

举个例子,假如你不喜欢使用管道符作为Categories列单元格值得分隔符,你想替换成为逗号后面加个空格作为分隔符。当然你可以先把多值单元格拆分然后再组合实现,但其实我们可以一步完成上述操作,点击Categories列下拉菜单选择“编辑单元格” |“转换”会出现如下的转换对话框:

OpenRefine

以上界面中最重要的部分是Expression(表达式), 这里可以输入小脚本,用来修改值。Language 项可以让我们选择表达式的语言,现在支持的语言有General Refine Expression Language(GREL), Jython (Java环境的Python语言), 和Clojure(函数型语言,类似Lisp语言),如果你对后两种语言较熟悉,你会发现书写表达式十分简单。但是,GREL是设计特别用来作简单转换的,所以本书我们将使用GREL。

在Preview页,我们能够同时看到初始值和转换后的值,这可以让你实时的调试转换表达式,能够立即看到效果。History页保存了你曾经用过的表达式,这样你后续可以直接重用原来的公式。History页还可以对公式打上星标,这样你还可以在Starred页找到它们。最后Help页对大多数表达式做了简介。

对话框底部,我们可以确定如果表达式在某个特定单元格中运行结果出错时怎么办。你可以选择保持原值、置空、使用错误值。另外,你也可以选择将转换表达式对单元格进行重复应用的次数,这个很有用,可以对处理结果再进行处理。比如,如果你有一个表达式,用来将首字母大写的单词删除,那么就可以利用这个重复功能将单元格中所有大写单词均删除。

回到我们的任务:将管道符修改为逗号后面加个空格。表达式处默认值是value,就像你猜到的那样,这代表原始值。让我们做个小实验看看会发生什么:输入1234作为新单元格值 。预览界面会更新,显示所有单元格值变为1234。这当然没什么意义,但对于你理解功能肯定有用。我们真正要做的是对原值按照我们的意愿进行替换。在Help页,我们找到实现上面意图的GREL函数叫做replace。因为我们想把管道符替换成逗号,所以我们输入value.replace(“|”, “, “)。这里我们对字符加了双引号,因为它们是字符串而非数字。预览界面会刷新单元格值,我们发现就是我们想要的结果,所以点击OK。只需要很短的时间,OpenRefine就转换好了Categories列的单元格内容,所有的管道符已经被替换。

在修改分隔符时需要特别小心,因为有可能分隔符恰恰是值内容中的一部分。上图出现的错误信息如下:Error: replace expects 3 strings, or 1 string, 1 regex, and 1 string。所以错误的原因是我们传递replace参数时出错了,这是有些单元格为null所导致。确实,因为null并不是字符串,所以我们并不能对其中的字符进行操作。所以我们需要告诉OpenRefine只转换非空的单元格,这里可以在对这个列进行“文本过滤”,找出含有“|”的数据。

虽然管道符已经被替换成了逗号,但是Categories列还有一些其他问题存在。确实,有一些值内容中就包含重复内容,比如第14条记录:Didactic displays, Pearl hells, Buttons,Didactic displays。我们无法采用上面的重复项处理来解决,因为这里的重复是在一个单元格内的。打开Categories列的值转换窗口,输入如下表达式:value.split(“, “).uniques().join(“, “)。这个表达式看起来有点复杂,但是分开解读还是比较容易理解的。首先第一个我们将值按照“,”分割(逗号后面跟一个空格),然后使用uniques函数去重,最后再把内容重新连接到一起。当你点击OK后,OpenRefine会完成操作并且提示你有多少单元格进行了该项操作。

3.5 增加源列

假设我们希望增加一列,这一列是对应分类单元格中分类计数。点击Categories列,选择“编辑列”|“由此列派出新列”会弹出一个类似单元格转换的对话框,这里提示需要一个列名。在“新列名称”中输入Category Count。现在我们可以创建分类计数的表达式了。因为分类是被某个字符分隔的(管道符或者逗号,如果你学习过上一点的话),我们可以简单的将它们拆分然后计数。表达式如下:value.split(“,”).length() ,Preview 面板会显示结果,如果发现没有问题,那么点击OK.确认。

OpenRefine

OpenRefine增加了一列新列,这样我们就可以使用Category Count列来分析我们的数据。比如,我们可以对CategoryCount列进行文本透视:“归类”|“文本归类”. 因为我们只是想对每个独立的计数数量进行分析,而不是分析其值域。所以这里使用文本透视比较简单,虽然这列数据是数字格式。左侧透视界面会显示究竟分类数如何分布,如下图所示:

OpenRefine

我们发现有一点比较奇怪:有一栏的值是(blank)。如果我们点击下,我们发现所有的记录Categories字段为空,那么为什么Category Count不显示为0呢?答案是对于空值单元格,这个转换表达式会导致一个错误,因为没有值可以被分割。上一点的学习中,我们可能能够做到在转换前把这些行过滤掉了。不过我们这里尝试修复这些行。在透视界面中点击(blank),点击Category Count 下拉菜单: “编辑单元格”|“转换” 如果我们在表达式窗口中输入0并且点击OK,那么这些空值单元格就会变成0。那些行会消失,别奇怪,因为我们在透视中还选中着blank,点击过滤项 0(或者其他你像选择的),你就能 得到你想要的数据。

3.6 拆分列

本章开始的时候,我们演示了如何将一个单元格中的多值内容拆分成多行。但是,有时候这并不能解决问题。现有例子中,单元格中的多个分类其实属于同一个属性:分类间比较相似,并且顺序可以互换。但是如果多值单元格中的内容属于不同的类别的话情况就不同了。比如,当一个clients表格包含电话字段但是没有email字段,而某一个客户却同时又上面两种信息。结果这个人的电话信息和email信息可能会被放在一起,用斜杠分隔。

在Powerhouse Museum数据集中,我们也在很多列种发现这种情况。比如,在Provenance字段,我们能够同时发现designers, makers等等信息。如果我们能够把它们拆分成不同的列的话,我们分析就会更加有意义。我们可以点击Provenance (Production)列下拉菜单: “编辑列”|“分割此列”,我们能够看到如下的拆分对话框:

OpenRefine

我们可以选择按照曾经介绍的“按分隔符”分割, 还可以按照“按长度”分割。按长度分割在数据结构固定的时候特别有用,如1987 en-us,X/Y这样的类型,其没有固定的分隔符(或者根本没有)。当然,本例中我们使用分隔符。一个有趣的选项是设置一个限制,因为可能有分割成很多列,所以设置下限制比如5会比较明智。千万不要忘记设置一个分隔符,比如这里设置成“|”。你可以选择让OpenRefine猜测下分割后的单元格类型(比如分割后可能会出现数字),还可以让OpenRefine删除原来的列。

在你点击OK后,你会发现OpenRefine将原来叫做Provenance (Production) 的列被替换成为了很多叫做 Provenance(Production) 1, Provenance (Production) 2等等的列。这些列可以后续按照实际含义修改名称。并不是所有列都有内容,只有那些至少含有5项的单元格分割后才会所有单元格都有内容。虽然分割后不会超过你设置的最大列数目,但是还是会产生分割前的单元格分项较少,导致无法填满所有的列的情况。

我们再对Object Title列进行分割,我们发现该列中有些单元格是以数字开头的。如果把数字从中剥离出来应该十分有意义。首先,让我们先过滤出这些数字开头的项。点击Object Title 下拉菜单中的Text filter.我们这里写上以数字开头的正则表达式^\d.这个表达式告诉过滤器从开头(^)进行查找并且寻找数字(\d)。别忘了勾选regular expression 。否则 OpenRefine 会把正则表达式当成字符查找。现在我们就得到了那些以数字开头的项。

OpenRefine

点击Object Title下拉菜单,然后选择:“编辑列”|“分割此列”,现在我们使用一个空格作为分隔符,设置我们最多要2列然后点击OK. 数字和标题被分割成了两列。

3.7 行列转换

有时候数据并不是以你料想的方式在行和列中分布。确实,有很多种数据排布方式,这取决于具体的情况。比如在Powerhouse Museum数据集中,有一些很多维度的列:Height, Width, Depth, Diameter和Weight.但是,并不是所有这些列中都有数据,所以如果按照这种方式排布耗时耗力。一个替代方法是将这5列转换成2列:一列包含维度名称(比如Height 或 Weight),另一列包含量度值(比如35mm 或 2kg)(有点类似于key-value形式)。

这里我们想做的就是将这些列转换成行。为了做到这点,点击Height 列下拉菜单:“变换” |“将不同列中的单元格转换为行”,会弹出如下对话框:

OpenRefine

左侧“来源列”栏中选择需要转换的开始列,Height 列已经被选中是因为我们就是点击这一列开始操作的。“目的列”栏选择停止转换的列。这两列中间的所有列就是需要转换的列(所以你需要首先将这些列放在一起).这里我们选择Weight,所以这两列及其中间的列将会被转换。

右侧,我们可以选择转换列中的变化内容。“两个新列”栏有两项设置, 比如Dimension和 Measurement。另外“一个新列”栏可以将名称和值都放到一个单元格中,但是这样做后续分析可能比较麻烦。当所有设置项设置好后,点击Transpose 开始转换。5列数据将转换成2列,如下所示:

OpenRefine

请注意,这里OpenRefine需要设置成“记录”模式以保证信息关联。所以,如果某行有不同的量度信息,那么转换后它们会占据多行。可能你希望能够看到所有5个值,即使有些是空值。那么你可以选择在转换对话框中去掉Ignore blank cells勾选。

我们还可以通过执行“变换”|“取键值列组合成列”来实现反向转换。但是,这个操作对于空单元格十分敏感,所以必须小心。你可能已经能够将Provenance列(名称/值配对出现)转换成全列格式。

3.8 小结

本章介绍了一些高级的数据操作。对于多值单元格我们有很多处理方法:当它们的值属于同一类型的话,我们可以将它们分割成多行;当它们意义不同的话,我们可以将它们分割成多列。另外我们也学习了OpenRefine中的一种特殊模式:记录模式。在记录模式下,属于同一主体的多个行被认为是一个整体,这样就能让我们进行更高效的操作。

我们也介绍了聚类,这在单元格值本身含义一样但是拼写不同时非常有用。你甚至可以定义自己的转换操作,或者基于已存在的列创建新列。最后,我们学习了如何进行合适的行列转换。总的来说,本章介绍了OpenRefine的高级操作,这些操作隐藏在简单的界面下面。到现在为止,你基本可以称得上是OpenRefine高手了。

第四章 数据集关联(略)

本章主要学习如何 与URL进行关联。本章中,介绍了如何使互相孤立的数据集建立联系。一方面,你可以对单内容字段进行解析,这样可以使得这些单元格与URL对应,并且能够在线查询详细信息。你也可以使用内置的Freebase解析或者安装RDF扩展包 来实现对链接数据的解析。另一方面,你也可以使用扩展包进行抽取单名称项,这可以使OpenRefine对单元格内的内容进行查询,并且找到每个内容小项的URL。最后,你的数据集会与其他数据集建立丰富的联系,使之在发布时变得很有价值。

4.1 使用Freebase解析值

字符和URL之间的转换,OpenRefine内置Freebase 解析(略)

4.2 安装扩展包(略)

4.3 增加解析服务(略)

4.4 与关联数据进行解析(略)

OpenRefine

第五章 正则表达式和GREL(略)

----------------------------

个人总结,openrefine最大的特点有如下的几个:

1、在数据导入的时候,可以根据数据类型将数据转换为对应的数值和日期型等。

2、相似单元格聚类,可以根据单元格字符串的相似性来聚类,并且支持关键词碰撞和近邻匹配算法。

3、在单元格值替换时,能够对表达式运行出错的处理,如保留原值、置空或者使用错误值,另外还可以控制表达式运行的次数。

4、并没有传统意义上的两个数据集之间的关联查询和处理,如mysql中的join操作

5、数据集关联主要是对内容做URL的关联,在目前所接触到的场景中没有用到该功能。

参考资料:

1、《Using OpenRefine》翻译

2、相关学习资料:https://pan.baidu.com/s/1pLXEPvl 密码: cpab

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,086
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,561
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,410
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,183
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,820
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,903