上一篇SVD 原理文章详细分析了SVD模型理论,本片主要看看SVD的对应分析,这种分析方法适用于矩阵型数据,用途广泛。
相比较对应分析,SVD能同时实现R型和Q型分析,而且在解析经济结构,分析隐藏因子方面表现比较好。在文本词频方面也表现较好,能够分离出代表含义词汇和文章。
当然,单独使用SVD难以形成完整的分析,我们还可以结合其他方法,比如Kmeans,cluster形成完整的分析。
这里使用的原始数据来自于中国统计年鉴,各省经济类农产品产出数据。不哆嗦了,先上源代码.
这里是该博客用到的数据
一 R语言分析
library(data.table)
library(ggplot2)
data =read.csv2(file = "C:/Drawsky/Book1.csv",head=T,sep=",",dec = ".")# 读取源
data[is.na(data)]=0 # 缺失数据表示产出为0。
rowname=data[,1]
data[,1]=NULL
#data=as.data.frame(sapply(X = data,FUN = function(x)x=(x-mean(x))))#如果需要的话,可以中心化
name=stringr::str_replace_all(string = names(data),pattern = "\\.|X",replacement = "")#名称有点乱,整理一下。
names(data)<-name
row.names(data)=rowname
res=svd(x = data)# SVD分解
k=sum(cumsum(res$d)/sum(res$d)<0.85)+1# 计算隐藏因子的数量
#对应分析,将样本和指标合并,SVD分解后的数据处理,将原数据的样本和指标视为新的样本,隐藏因子转化成新的指标。
names1=c(names(data),stringr::str_replace_all(string =rowname,pattern = " ",replacement = ""))
newdata=rbind(res$v,res$u)[,1:k]
row.names(newdata)=names1
lambda=rep(x = res$d[1:k],each=n)
lambda = matrix(data = lambda,nrow = n)
hivefactot=newdata*lambda
hivefactot<- as.data.frame(newdata)
hivefactot$V8=names1
## SVD分解后的数据处理完毕
## 隐藏因子如果三个以内的话,可以直接作图显示,如果超过四个,怎么作图呢?
## 化解方法是kmeans ,我们看不见,但是可以分类,看看那些地区和产品在隐藏因子上表现的比较像。
km=kmeans(x = hivefactot[,1:k],centers = k,iter.max = 10,nstart = 40)
class=km$cluster
split(names(class),f = class)
看看分析结果
> split(names(class),f = class)
$`1`
[1] "芝麻" "麻类" "烟叶" "蚕茧" "茶叶" "葡萄" "北京" "天津" "山西" "内蒙古" "辽宁" "吉林" "上海" "江苏" "浙江" "安徽" "福建" "西藏" "甘肃" "青海" "宁夏"
$`2`
[1] "香蕉" "广东" "广西" "海南" "云南"
$`3`
[1] "薯类" "梨" "河北" "重庆" "四川" "贵州"
$`4`
[1] "苹果" "山东" "陕西"
$`5`
[1] "油菜籽" "柑桔" "江西" "湖北" "湖南"
$`6`
[1] "豆类" "花生" "黑龙江" "河南"
$`7`
[1] "棉花" "甜菜" "新疆"
看看农产品和地区的对应关系,是不是跟没分析一样?要的就是这个效果,符合常识;
第一组,基本上是没什么特色的组,产品那哪儿都产,对应地区也一样,什么都有特点不突出。
第二到,第七组,特色相当鲜明。
注: 数据分析的结果如果令人费解的话,要么分析方法用错了,要么出现了以前不知道的东西,应当仔细甄别;当然还有可能数据是假的。
R 中的kmeans分类,初始挑选中心带有一定的随机性,多次执行会发现有些省份和农产品分类一直在摇摆。可以通过设置迭代次数和扩大初始化中心的所需的样品数,这样可以形成稳定的分类。
二 Spark做SVD分析
先吐槽一下spark 的数据类型,转换实在麻烦,MLlib和ML的类型同名还不能相互转换,很搞,很搞,很搞。。。
不说废话了了直接上源码。
Java源码
public static void svd(SparkSession spark) {
//读取CSV文件
Dataset<Row> data = spark.read().option("header", true).option("encoding", "gb2312").option("nullValue", 0)
.csv("C:/drawsky/Book1.csv");
//收集指标名称,行名称(这里就是省份)
List<String> colname =Arrays.asList( data.columns());
List<String> rowName = data.select(colname.get(0)).collectAsList().stream().map(e -> e.getString(0).replaceAll("\b", ""))
.collect(Collectors.toList());
colname=colname.stream().map(e->e.replaceAll("[#\b]+", "")).collect(Collectors.toList());
List<String> allname = new ArrayList<String>();
allname.addAll(colname);
allname.addAll(rowName);
allname.remove(0);
//转换成Rdd
JavaRDD<Row> dataRdd = data.toJavaRDD();
JavaRDD<Vector> set = dataRdd.map(e -> {
int n = e.length();
double[] s = new double[n - 1];
for (int i = 1; i < n; i++) {
if (null == e.getString(i)) {
s[i - 1] = 0;
} else {
s[i - 1] = Double.parseDouble("" + e.getString(i));
}
}
return Vectors.dense(s);
});
//转换成矩阵形式
RowMatrix mat = new RowMatrix(set.rdd());
//SVD分解
SingularValueDecomposition<RowMatrix, Matrix> svd = mat.computeSVD(16, true, 1E-9d);
RowMatrix U = svd.U(); // The U factor is a RowMatrix.
Vector s = svd.s(); // The singular values are stored in a local dense
// vector.
Matrix V = svd.V();
// 检验一下SVD分解,看看分解后的矩阵相乘能不能回到原始数据
// List<Vector> hehe=U.multiply(Matrices.diag(s)).multiply(V.transpose()).rows().toJavaRDD().collect();
//为下一步kmeans准备数据
DenseMatrix s1 = (DenseMatrix) Matrices.diag(s);
V = V.multiply(s1);
U = U.multiply(s1);
//V 是本地矩阵,需要转换成Rdd先转换成List<Bean>
List<Bean> v = new ArrayList<>();
Iterator<Vector> it = V.rowIter();
while (it.hasNext()) {
v.add(new Bean(it.next()));
}
//V 转换成RDD
JavaRDD<Vector> vv = spark.createDataFrame(v, Bean.class).toJavaRDD().map(f -> (Vector) f.get(0));
//合并UV
JavaRDD<Vector> res = vv.union(U.rows().toJavaRDD());
//Kmeans 聚类参数
int numClusters = 7;
int numIterations = 100;
//Kmeans 计算分类样本中心
KMeansModel clusters = KMeans.train(res.rdd(), numClusters, numIterations,0,"k-means||",40);
//分类
List<Integer> cla = clusters.predict(res).collect();
//将分类转换成人看得懂的形式
Map<Integer, List<String>> o = new HashMap<Integer, List<String>>();
for (int i = 0; i < cla.size(); i++) {
if (!o.containsKey(cla.get(i))) {
o.put(cla.get(i), new ArrayList<>());
}
o.get(cla.get(i)).add(allname.get(i));
}
System.out.println("分类标签:"+cla);
System.out.println("样本名称:"+allname);
System.out.println("分析结果:\n"+o);
// data.show();
// hehe.stream().map(e->e.toJson()).forEach(e->System.out.println(e));
}
bean.java 的源码
package com.SparkTest;
import java.io.Serializable;
import org.apache.spark.mllib.linalg.Vector;
public class Bean implements Serializable {
Vector v =null;
public Bean(Vector s){
this.v=s;
}
private static final long serialVersionUID = 126198628520278041L;
public Vector getV() {
return v;
}
public void setV(Vector v) {
this.v = v;
}
}
运行结果
分类标签:[3, 4, 2, 0, 4, 3, 3, 2, 3, 3, 3, 1, 5, 0, 3, 5, 3, 3, 0, 0, 3, 0, 3, 3, 3, 3, 3, 3, 4, 4, 1, 0, 4, 4, 5, 5, 3, 4, 4, 3, 3, 3, 6, 3, 3, 3, 2]
样本名称:[豆 类, 薯 类, 棉 花, 花 生, 油菜籽, 芝 麻, 麻 类, 甜 菜, 烟 叶, 蚕 茧, 茶 叶, 苹 果, 柑 桔, 梨, 葡 萄, 香 蕉, 北 京, 天 津, 河 北, 山 西, 内蒙古, 辽 宁, 吉 林, 黑龙江, 上 海, 江 苏, 浙 江, 安 徽, 福 建, 江 西, 山 东, 河 南, 湖 北, 湖 南, 广 东, 广 西, 海 南, 重 庆, 四 川, 贵 州, 云 南, 西 藏, 陕 西, 甘 肃, 青 海, 宁 夏, 新 疆]
分析结果:
{0=[花 生, 梨, 河 北, 山 西, 辽 宁, 河 南],
1=[苹 果, 山 东],
2=[棉 花, 甜 菜, 新 疆],
3=[豆 类, 芝 麻, 麻 类, 烟 叶, 蚕 茧, 茶 叶, 葡 萄, 北 京, 天 津, 内蒙古, 吉 林, 黑龙江, 上 海, 江 苏, 浙 江, 安 徽, 海 南, 贵 州, 云 南, 西 藏, 甘 肃, 青 海, 宁 夏],
5=[柑 桔, 香 蕉, 广 东, 广 西],
6=[ 陕 西]
}
结果和R运行的有点差别,貌似没有R分析的效果好,调了很久的参数,百思不得七姐呀。。。要知道,Spark里面哥用的是Kmeans++算法呀,这么烂??