上一篇博客主要讲解了Weka开发环境的搭建,从本博客开始就正式进入Weka开发。
进行数据挖掘,首先要关注的是数据,这篇博客主要讲解Weka的数据格式和在Weka中如何操作数据。
Weka数据格式
Weka的专有数据格式是ARFF,下面是Weka自带的data文件夹里的一个ARFF文件
% ARFF file for the weather data with some numric features
%
@relation weather
@attribute outlook {sunny, overcast, rainy}
@attribute temperature real
@attribute humidity real @attribute windy {TRUE, FALSE} @attribute play {yes, no}
@data
%
% 14 instances
%
sunny,85,85,FALSE,no
sunny,80,90,TRUE,no
overcast,83,86,FALSE,yes
rainy,70,96,FALSE,yes
rainy,68,80,FALSE,yes
rainy,65,70,TRUE,no
overcast,64,65,TRUE,yes
sunny,72,95,FALSE,no
sunny,69,70,FALSE,yes
rainy,75,80,FALSE,yes
sunny,75,70,TRUE,yes
overcast,72,90,TRUE,yes
overcast,81,75,FALSE,yes
rainy,71,91,TRUE,no
其实不难看出,Weka的专有数据格式和CSV文件非常相似,CSV即为逗号分隔符文件,最大的区别在于,Weka头部有每列数据的属性标识。常见的包头如下所示:
@RELATION iris
@ATTRIBUTE sepallength NUMERIC
@ATTRIBUTE sepalwidth NUMERIC
@ATTRIBUTE petallength NUMERIC
@ATTRIBUTE petalwidth NUMERIC
@ATTRIBUTE class {Iris-setosa,Iris-versicolor,Iris-virginica}
- Weka数据格式分析
① 其中@RELATION后面是关系名,如果你只打开一个文件,就是该文件的文件名。
@relation <relation-name>
<relation-name>一个字符串
如果这个字符串包含空格,它必须加上引号(指英文标点的单引号或双引号)
② @ATTRIBUTE指的是该列的属性,包括NUMERIC指的是数值型变量等以下四种变量类型:
numeric 数值型
<nominal-specification> 分类(nominal)型
string 字符串型
date [<date-format>] 日期和时间型
其中NUMERIC一般用于连续型数值变量,nominal一般是离散型类别变量。
③ 对于@class类型。指的是类别变量,其所有的取值被大括号包括,如下所示:
由<nominal-specification>组成的类别名放在花括号中:
{<nominal-name1>, <nominal-name2>, <nominal-name3>, ...}
实际数据类别只能是花括号中的某一个
④@date指的是日期型变量,在数据挖掘中,日期型变量可以直接相加减以获取时间间隔,
@attribute <name> date [<date-format>]
<name>是属性的名称
<date-format>是一个字符串来规定该怎样解析和显示日期或时间的格式
默认的字符串格式是ISO-8601所给的日期时间组合格式“yyyy-MM-ddTHH:mm:ss”。
⑤除此之外,还有@data标识符,用来表明数据的具体内容:
@data
sunny,85,85,FALSE,no
?,78,90,?,yes
每个实例占一行
实例的各属性值用逗号“,”隔开
缺失值(missing value)用问号“?”表示
⑥ Weka中针对稀疏数据也有更加方便的表示,即稀疏变量格式:有的时候数据集中含有大量的0值,这个时候用稀疏格式的数据存贮更加省空间。
如对于下述数据:
@data
0, X, 0, Y, "class A"
0, 0, W, 0, "class B"
可以使用稀疏格式表达为:
@data
{1 X, 3 Y, 4 "class A"}
{2 W, 4 "class B"}
实例中每一个非0的属性值用<index> <空格> <value>表示
Weka官方关于ARFF文件格式的介绍可以在下面地址找到:
http://weka.wikispaces.com/ARFF+%28book+version%29
Weka专有格式和其他格式之间的相互转换
有时候我们可能有其他格式的数据,想转化为ARFF格式的文件,或者想把ARFF格式的文件转化成其他文件格式,Weka也可以很好的支持。
一种方式是使用GUI,打开文件后另存为,另一种方式是在代码中使用Weka的ConverterUtils工具类直接实现,下面贴出我自己写的JAVA的代码:
public static void fileTypeConverter(String inputFile, String outputFile) {
File input = new File(inputFile);
File output = new File(outputFile);
AbstractFileLoader loader = ConverterUtils.getLoaderForFile(input);
AbstractFileSaver saver = ConverterUtils.getSaverForFile(output);
try {
loader.setSource(input);
Instances data = loader.getDataSet();
System.out.println(data);
saver.setInstances(data);
saver.setFile(output);
saver.setDestination(output);
saver.writeBatch();
} catch (IOException e) {
e.printStackTrace();
}
}
需要注意的是,在使用转换功能之前,应该使用Weka的Filter指定每一列的属性类别,免得所有列都按照默认的NUMRIC类型处理。对于Filter和Instances的介绍将在接下来几篇博客里面继续讲解。
CSV和LibSVM格式互转JAVA代码:
- LIBSVM to CSV
加上了错误行处理,可以自己写正则表达式匹配不对的行并抓出来:
public static void libsvmToCsv(String inputFile, String outputFile, int numOfAttributes, ArrayList indexOfNominal) {
BufferedReader br = null;
BufferedWriter bw = null;
String s;
Pattern patternIsNum = Pattern.compile("[0-9.]*");
Pattern patternIsSciNotation = Pattern.compile("^((\\d+.?\\d+)[E]{1}(\\d+))$");
long lineNum = 0;
try {
br = new BufferedReader(new FileReader(inputFile));
bw = new BufferedWriter(new FileWriter(outputFile));
int errorCount = 0;
do {
s = br.readLine();
lineNum++;
boolean errorFlag = false;
if (s == null)
break;
String[] result = new String[numOfAttributes + 1];
StringBuffer sb = new StringBuffer();
for (int i = 0; i < numOfAttributes; i++) {
result[i] = "0,";
}
String temp[] = s.split(" ");
result[numOfAttributes] = temp[0];
for (int i = 1; i < temp.length; i++) {
String[] innerTemp = temp[i].split(":");
char[] indexArray = innerTemp[0].toCharArray();
if (innerTemp.length > 1) {
int attributesIndex = Integer.parseInt(innerTemp[0]);
Matcher isSciNotation = patternIsSciNotation.matcher(innerTemp[1]);
Matcher isNum = patternIsNum.matcher(innerTemp[1]);
if (isSciNotation.matches()) {
result[attributesIndex - 1] = new BigDecimal(innerTemp[1]).toPlainString() + ",";
} else if (isNum.matches() || indexOfNominal.contains(attributesIndex)) {
result[attributesIndex - 1] = innerTemp[1] + ",";
} else {
errorFlag = true;
}
}
}
if (errorFlag) {
errorCount++;
System.out.println("第" + errorCount + "条-----第 " + lineNum + "行------ " + s);
}
else {
for (int j = 0; j < numOfAttributes + 1; j++) {
sb.append(result[j]);
}
bw.write(sb.toString());
bw.newLine();
}
} while (s != null);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
br.close();
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}