Java - Quantile normalization implementation

在工作上有使用到Quantile normalization的統計,不過在這邊不是要說明這個統計的意義,而

是說明如何利用Java來實作。至於要理解他的概念可以至Wiki

假設有一個二維陣列如下:

double exp[][] = {{23,54,95}, 
                  {10,84,61}, 
                  {44,90,36}, 
                  {88,12,63}};

一、排序處理

在這邊由於我們要去排序3個column的資料,分別為23,10,44,88 ; 54,84,90,12 ; 95,61,36,63

,而一般陣列在讀取時,比較好讀的順序是23, 54, 95,如此我們可以先將此陣列做

transpose的動作,將exp[3][4] => exp[4][3]

二、做平均,並更換值

依據Quantile的說明,這exp[3][4] 需要針對排序後的資料去做一個row一個row的平均,

排序後需為:

23,54,95         10,12,36  => average (19.3)

10,84,61         23,54,61  => average (46.0)
           =>
44,90,36         44,84,63  => average (63.7)

88,12,63         88,90,95  => average (91.0)


再來是算出來的平均去替換排序後同一個row的值,如10,12,36需替還成19.33,以此類推。

19.3,19.3,19.3

46.0,46.0,46.0

63.7,63.7,63.7

91.0,91.0,91.0


三、回復排序前的位置

最後經由平均去替換值,還需要將值回復成原來的位置,如原本[0][0] = 23,23後來是排在

[0][1]且值變成了46,因此[0][0] = 46,以此類推。回復位置後的array如下:

46.0,46.0,91.0

19.3,63.7,46.0

63.7,91.0,19.3

91.0,19.3,63.7


這就是最後的結果。

好,上面說完了大概的流程,回歸程式的部分,撇開目視的類推,在這邊我們會先

做transpose,如下:

public static double[][] transpose(double exp[][]){
    double trans[][] = new double[exp[0].length][exp.length];
    for(int j = 0 ; j < exp[0].length ; j++){
        for(int i = 0 ; i < exp.length ; i++){
            trans[j][i] = exp[i][j];
        }
    }
    return trans;
}

如此將得到3x4

23,10,44,88

54,84,90,12

95,61,36,63


再來我們將針對排序的議題,利用Arrays.sort幫我們輕鬆做到,但我們需要去定義

一個Comparator interface(規則),並且將陣列內的值存進另外定義的class Quantile,此類別有

兩個欄位,分別是存值及位置(此位置為transpose後的陣列位置)

double exp2[][] = transpose(exp);
Quantile n_exp[][] = new Quantile[exp2.length][exp2[0].length];
for(int i = 0 ; i < exp2.length ; i++){
    for(int j = 0 ; j < exp2[0].length ; j++){
       n_exp[i][j] = new Quantile(j, exp2[i][j]);
    }
}

Comparator interface (compare的部分就不細部說明,在此篇有特別提到)

class QuanCompare implements Comparator{
    private String methodName;
    public QuanCompare(String methodName){
       this.methodName = methodName;
    }
    @Override
    public int compare(Quantile o1, Quantile o2) {
      // TODO Auto-generated method stub
      try{
         Method method1 = o1.getClass().getDeclaredMethod(this.methodName);   
         Method method2 = o2.getClass().getDeclaredMethod(this.methodName);
         Object obj1 = method1.invoke(o1);
         Object obj2 = method2.invoke(o2);
         if(methodName.equals("getExpvalue")){
             if((double)obj1 > (double)obj2){
                return 1;
             }else{
                return -1;
             }
         }else{
             if((int)obj1 > (int)obj2){
                return 1;
             }else{
                return -1;
             }
          }
      }catch(Exception e){
          e.printStackTrace();
      } 
      return 0;
    }
}

Quantile Class如下

class Quantile{
    private int pos;
    private double expvalue;
    public Quantile(int pos, double expvalue) {
       super();
       this.pos = pos;
       this.expvalue = expvalue;
    }
    public int getPos() {
       return pos;
    }
    public double getExpvalue() {
       return expvalue;
    }
    public void setPos(int pos) {
       this.pos = pos;
    }
    public void setExpvalue(double expvalue) {
       this.expvalue = expvalue;
    }
}

這個n_exp的二維物件陣列,除了擁有值且具有position的部分。

首先我們利用Arrays.sort進行值的排序,並且計算average,如下:

double avg[] = new double[exp.length];
for(int i = 0 ; i < exp2.length ; i++){
    Arrays.sort(n_exp[i], new QuanCompare("getExpvalue"));
    for(Quantile t : n_exp[i]){
       avg[x] = avg[x]+t.getExpvalue();
       if(i == exp2.length-1){
          avg[x] = avg[x]/exp2.length;
       }
       x++;
    }
    x = 0;
}

請注意由於我們將array做了transpose,因此我們要計算的average是看column的部分

10,23,44,88

12,54,84,90

36,61,63,95

在來是做值的替換,如下:

for(int i = 0 ; i < exp2.length ; i++){
    for(Quantile t : n_exp[i]){
        t.setExpvalue(avg[x]);
        x++;
    }
    x = 0;
} 

轉換後,如下

19.3,46.0,63.7,91.0 

19.3,46.0,63.7,91.0

19.3,46.0,63.7,91.0 

再來是透過position的排序,值回復原來的位置

for(int i = 0 ; i < exp2.length ; i++){
    Arrays.sort(n_exp[i], new QuanCompare("getPos"));
}

回復後,如下:

46.0,19.3,63.7,91.0 

46.0,63.7,91.0,19.3

91.0,46.0,19.3,63.7

最後就是將二維物件陣列的值assign給exp2 array,並且transpose回原來的array

for(int i = 0 ; i < exp2.length ; i++){
    for(Quantile t : n_exp[i]){
       exp2[i][x] = t.getExpvalue();
       x++;
    }
    x = 0;
}
double new_exp[][] = transpose(exp2);

再來是print new_exp得到,

46.0,46.0,91.0

19.3,63.7,46.0

63.7,91.0,19.3

91.0,19.3,63.7


如此就大功告成囉!篇幅有點冗長,若有弄錯的地方請不吝指正!

留言