简单的数据分析
一、问题:怎么用Java语言从一张二维数据表中做出数据透视和筛选的操作呢
在Excel中拿到一张二维的数据表,要提取其中的有用的信息,常用的方法就是根据条件筛选和数据透视获取聚合后的信息,但现在在Java环境中分析类似的结构,思路其实是一样的。如果学过sql语言,会发现,和sql语言的分组聚合也是一个思路。
怎样从一张二维数据表中做出类似的分析效果呢?
二、思路:
1.分组和聚合,筛选行和列,这些操作都是在原数组上操作的,一套简单的完整的流程是:选取想要的列,对目标列进行分组,聚合结果列。这是一个传递的关系,每一步操作都是在上一步操作结果接着做的。因此,在写分析类时,要每个对应的方法最终返回的值是执行完所有操作后的对象本身,这是第一个注意点。
2.操作数组时,如果在原数组中直接操作的话,会涉及到很多删掉无用列无用行的操作,简洁的方法是新建一个数组,把操作得到的数据填到新数组里,再把新数组赋值给原数组。这是第二个注意点。
3.拆分步骤,数据透视,第一步是先选取对应的列,其他列不要;第二部,对目标列(聚合列)进行聚合,把结果列根据目标列的值进行列转行的操作;第三部,对结果列的数据进行计数、求和、平均值等操作;第四步,调用方法读取结果
三、新建Array类
1.定义一个二维数组
2.把大于等于小于介于等定义为常量,这样在输入过滤参数时更方便
3.构造方法
public static final int A=0;//<
public static final int B=1;//<=
public static final int C=2;//=
public static final int D=3;//>=
public static final int E=4;//>
public static final int F=5;//介于
private String[][] data;
public Array(String[][] data) {
this.data = data;
}
四、优化:
为避免输入参数不规范导致异常报错,最好写几个方法提前验证下参数的有效性:
1.验证选取的列的范围是否超出
2.使用常量时,验证是否在常量范围内
3.验证条件范围参数是否符合规则(大于等于小于介于等的规则)
4.列过滤去重
private boolean isIxInValid(int ix){
final int COL=data[0].length;
return ix<0||ix>=COL;
}
private boolean isSignInValid(int sign){
return sign<0||sign>5;
}
private <T> boolean isArgInValid(T[] t){
return null==t||t.length==0;
}
五、具体方法类
1.列过滤
单独写个方法,保留不重复列,重复值过滤掉
获取不重复的列的编号,存在数组里,这里使用了动态参数
新建二维数组操作
把原数组里的列赋值给新数组
把新数组传给原数组(避免在原数组上操作,麻烦)
//列过滤
public Array map(int...ixs){
ixs = distinct(ixs);
String[][] copy = new String[data.length][ixs.length];
for (int i = 0; i < copy.length; i++) {
for (int j = 0; j < ixs.length; j++) {
copy[i][j] = data[i][ixs[j]];
}
}
data = copy;
return this;
}
2.行过滤-数值
思路:过滤行,一般是提取满足某项条件的值或文本
传入3个参数:哪一列,满足哪些条件
把表里对应列的数字强转为double
遍历每一行的该列,如果满足条件,传到新数组里
//行过滤
public Array filter(int ix,int sign,Double...t) throws Exception {
if (isIxInValid(ix)||isSignInValid(sign)||isArgInValid(t)){
throw new Exception("输入的参数异常");
}
int size = 0;
for (int i = 0; i < data.length; i++) {
double d = Double.parseDouble(data[i][ix]);
double a = t[0];
if (sign==A&&d<a||sign==B&&d<=a||sign==C&&d==a||sign==D&&d>=a||sign==E&&d>a||sign==F&&d>a&&d<t[1]){
data[size++] = data[i];
}
}
String[][] copy = new String[size][];
System.arraycopy(data,0,copy,0,size);
data = copy;
return this;
}
3.行过滤-字符串
思路:字符串一般是判断是否等于
传入两个参数:哪一列,要输入的动态参数数组
遍历二维数组,如果满足条件,提取出
//字符串列过滤
public Array filter(int ix,String...t) throws Exception{//这一行的ix列下面的值要符合sign特征的t的值
if (isIxInValid(ix)||isArgInvalid(t)){
throw new Exception("ix或t异常");
}
//验证数据是不是字符串或者数字
int size = 0;
for (int i = 0; i < data.length; i++) {
boolean exist = false;//是否重复、是否存在,就用布尔值
for (String s : t) {
if (data[i][ix].equals(s)){
data[size++] = data[i];
break;
}
}
}
String[][] copy = new String[size][];
System.arraycopy(data,0,copy,0,size);
data = copy;
return this;
}
4.分组(数据透视,但不计算)
思路:过滤出要透视的列,对分组列去重;然后再生成一个数组,所有项放进去(可能和分组列是一列)
遍历数组先去重,保留不重复项,生成一个不重复项的数组,作为输出结果的第一个值
再次遍历数组,如果等于第一个distinct数组,则在后面添加一个1(如果是两列,则添加第二列的值),生成一个新数组;多个新数组放到二维数组中,生成一个新的二维数组
//分组,数据透视
public Array groupBy(int ix) throws Exception {
if (data[0].length==0||data[0].length>2){
throw new Exception("分组列为1-2");
}
String[] distinct = new String[data.length];
int size = 0;
for (int i = 0; i < data.length; i++) {
if (size==0){
distinct[size++]=data[i][ix];
}else {
boolean notrepeat = true;
for (int j = 0; j <size ; j++) {
if (data[i][ix].equals(distinct[j])){
notrepeat = false;
break;
}
}
if (notrepeat){
distinct[size++] = data[i][ix];
}
}
}
String[][] copy = new String[size][];
for (int i = 0,_ix = ix==0 ? 1 : 0 , col = data[0].length; i <size ; i++) {
String[] row = new String[data.length+1];
int _size = 0;
row[_size++] = distinct[i];
for (int j = 0; j <data.length ; j++) {
if (data[j][ix].equals(distinct[i])){
row[_size++] = col==1?"1":data[j][_ix];
}
}
String[] _row = new String[_size];
System.arraycopy(row,0,_row,0,_size);
copy[i] = _row;
}
data=copy;
return this;
}
5.分组数据透视后的计数
思路:新建一个两列的数组,第一列就是分组后的第一列(分组名),第二列是对应行的长度减去第一个
//计数
public Array count(){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
copy[i][1] = data[i].length-1+"";
}
data = copy;
return this;
}
6.分组后找到最大最小值
思路:新建一个两列的数组,遍历分组后的行,把第一个数字拿出来和后面的依次比较,如果更大,赋给他。最小值同理。然后把得到的值放在新数组的第二列
//最大最小值,布尔值判断
private Array limit(boolean max){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
double limit = Double.parseDouble(data[i][1]),val;
for (int j = 2; j < data.length; j++) {
val = Double.parseDouble(data[i][j]);
if (max?val>limit:val<limit){
limit = val;
}
}
copy[i][1] = limit+"";
}
data = copy;
return this;
}
//最大值
public Array max(){
return limit(true);
}
//最小值
public Array min(){
return limit(false);
}
7.分组后求和求平均数
思路:新建一个两列的数组,遍历分组后的行,从第二个开始累加,得到的值赋给新数组的第二列;平均数则除以数组的长度减一
//求和求平均数
private Array cal(boolean avg){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
double sum = 0;
for (int j = 1; j <data[i].length ; j++) {
sum += Double.parseDouble(data[i][j]);
}
copy[i][1] = (avg?Math.round(sum/data[i].length-1):sum)+"";
}
data = copy;
return this;
}
//求平均数
public Array avg(){
return cal(true);
}
//求和
public Array sum(){
return cal(false);
}
六、执行
本地的数据调整为逗号为分隔符的txt文档,放在工程项目下,表结构为:
“姓名,性别,年龄,入学日期,毕业日期,就业薪资,籍贯,就业城市”
读取本地文档,需要用到字符输入流BufferReader,使用完关闭流即可,代码如下
public class Analysisy {
public static void main(String[] args) throws Exception {
String[][] data = new String[25][];
BufferedReader br =new BufferedReader(new FileReader("D:\\JavaProjects\\ClassStudy\\Javahigh\\JavahighTest\\analyasis\\myfile\\kb10.txt"));
String line = null;
int size =0;
while (null!=(line=br.readLine())){
data[size++] = line.split(",");
}
br.close();
Array array = new Array(data);
//输出第一列和第三列
// array.map(0,2).println();
//输出年龄大于28岁的人
// array.map(0,2).filter(1,4,28.0).println();
//输出籍贯为安徽的人的姓名性别
// array.map(0,1,6).filter(2,"安徽").println();
//求:不同性别的人数
// array.map(1).groupBy(0).count().println();
//求:不同籍贯的人数
// array.map(6).groupBy(0).count().println();
//求:不同就业地的人数
// array.map(7).groupBy(0).count().println();
//求:不同性别的平均薪资
// array.map(1,5).groupBy(0).avg().println();
//求不同籍贯的人的平均薪资
// array.map(6,5).groupBy(0).avg().println();
//求:不同就业地的人的平均薪资
// array.map(7,5).groupBy(0).avg().println();
}
}
七、方法类完整代码如下:
public class Array {
public static final int A=0;//<
public static final int B=1;//<=
public static final int C=2;//=
public static final int D=3;//>=
public static final int E=4;//>
public static final int F=5;//介于
private String[][] data;
public Array(String[][] data) {
this.data = data;
}
//验证是否重复
private int[] distinct(int[] ixs){
int[] copy = new int[ixs.length];
int size = 0;
for (int ix : ixs) {
if (size==0){
copy[size++]=ix;
}else {
boolean isrepeat = false;
for (int i = 0; i <size ; i++) {
if (ix==copy[i]){
isrepeat = true;
break;
}
}
if (isrepeat){
break;
}
copy[size++] = ix;
}
}
int[] copy2 = new int[size];
System.arraycopy(copy,0,copy2,0,size);
copy = null;
return copy2;
}
//判断列 参数 范围是否无效
private boolean isIxInValid(int ix){
final int COL=data[0].length;
return ix<0||ix>=COL;
}
private boolean isSignInValid(int sign){
return sign<0||sign>5;
}
private <T> boolean isArgInValid(T[] t){
return null==t||t.length==0;
}
//列过滤
public Array map(int...ixs){
ixs = distinct(ixs);
String[][] copy = new String[data.length][ixs.length];
for (int i = 0; i < copy.length; i++) {
for (int j = 0; j < ixs.length; j++) {
copy[i][j] = data[i][ixs[j]];
}
}
data = copy;
return this;
}
//行过滤
public Array filter(int ix,int sign,Double...t) throws Exception {
if (isIxInValid(ix)||isSignInValid(sign)||isArgInValid(t)){
throw new Exception("输入的参数异常");
}
int size = 0;
for (int i = 0; i < data.length; i++) {
double d = Double.parseDouble(data[i][ix]);
double a = t[0];
if (sign==A&&d<a||sign==B&&d<=a||sign==C&&d==a||sign==D&&d>=a||sign==E&&d>a||sign==F&&d>a&&d<t[1]){
data[size++] = data[i];
}
}
String[][] copy = new String[size][];
System.arraycopy(data,0,copy,0,size);
data = copy;
return this;
}
//分组,数据透视
public Array groupBy(int ix) throws Exception {
if (data[0].length==0||data[0].length>2){
throw new Exception("分组列为1-2");
}
String[] distinct = new String[data.length];
int size = 0;
for (int i = 0; i < data.length; i++) {
if (size==0){
distinct[size++]=data[i][ix];
}else {
boolean notrepeat = true;
for (int j = 0; j <size ; j++) {
if (data[i][ix].equals(distinct[j])){
notrepeat = false;
break;
}
}
if (notrepeat){
distinct[size++] = data[i][ix];
}
}
}
String[][] copy = new String[size][];
for (int i = 0,_ix = ix==0 ? 1 : 0 , col = data[0].length; i <size ; i++) {
String[] row = new String[data.length+1];
int _size = 0;
row[_size++] = distinct[i];
for (int j = 0; j <data.length ; j++) {
if (data[j][ix].equals(distinct[i])){
row[_size++] = col==1?"1":data[j][_ix];
}
}
String[] _row = new String[_size];
System.arraycopy(row,0,_row,0,_size);
copy[i] = _row;
}
data=copy;
return this;
}
//计数
public Array count(){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
copy[i][1] = data[i].length-1+"";
}
data = copy;
return this;
}
//最大最小值,布尔值判断
private Array limit(boolean max){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
double limit = Double.parseDouble(data[i][1]),val;
for (int j = 2; j < data.length; j++) {
val = Double.parseDouble(data[i][j]);
if (max?val>limit:val<limit){
limit = val;
}
}
copy[i][1] = limit+"";
}
data = copy;
return this;
}
//最大值
public Array max(){
return limit(true);
}
//最小值
public Array min(){
return limit(false);
}
//求和求平均数
private Array cal(boolean avg){
String[][] copy = new String[data.length][2];
for (int i = 0; i < copy.length; i++) {
copy[i][0] = data[i][0];
double sum = 0;
for (int j = 1; j <data[i].length ; j++) {
sum += Double.parseDouble(data[i][j]);
}
copy[i][1] = (avg?Math.round(sum/data[i].length-1):sum)+"";
}
data = copy;
return this;
}
//求平均数
public Array avg(){
return cal(true);
}
//求和
public Array sum(){
return cal(false);
}
//输出
public void println(){
for (String[] datum : data) {
for (String s : datum) {
System.out.print(s+"\t");
}
System.out.println();
}
}
}