Java - Quantile normalization implementation

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

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

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

  1. double exp[][] = {{23,54,95},
  2. {10,84,61},
  3. {44,90,36},
  4. {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的平均,

排序後需為:

  1. 23,54,95         10,12,36  => average (19.3)
  2.  
  3. 10,84,61         23,54,61  => average (46.0)
  4. =>
  5. 44,90,36     44,84,63  => average (63.7)
  6.  
  7. 88,12,63         88,90,95 => average (91.0)
  8.  

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

  1. 19.3,19.3,19.3
  2.  
  3. 46.0,46.0,46.0
  4.  
  5. 63.7,63.7,63.7
  6.  
  7. 91.0,91.0,91.0
  8.  

三、回復排序前的位置

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

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

  1. 46.0,46.0,91.0
  2.  
  3. 19.3,63.7,46.0
  4.  
  5. 63.7,91.0,19.3
  6.  
  7. 91.0,19.3,63.7
  8.  

這就是最後的結果。

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

做transpose,如下:

  1. public static double[][] transpose(double exp[][]){
  2. double trans[][] = new double[exp[0].length][exp.length];
  3. for(int j = 0 ; j < exp[0].length ; j++){
  4. for(int i = 0 ; i < exp.length ; i++){
  5. trans[j][i] = exp[i][j];
  6. }
  7. }
  8. return trans;
  9. }

如此將得到3x4

23,10,44,88

54,84,90,12

95,61,36,63

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

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

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

  1. double exp2[][] = transpose(exp);
  2. Quantile n_exp[][] = new Quantile[exp2.length][exp2[0].length];
  3. for(int i = 0 ; i < exp2.length ; i++){
  4. for(int j = 0 ; j < exp2[0].length ; j++){
  5. n_exp[i][j] = new Quantile(j, exp2[i][j]);
  6. }
  7. }

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

  1. class QuanCompare implements Comparator{
  2. private String methodName;
  3. public QuanCompare(String methodName){
  4. this.methodName = methodName;
  5. }
  6. @Override
  7. public int compare(Quantile o1, Quantile o2) {
  8. // TODO Auto-generated method stub
  9. try{
  10. Method method1 = o1.getClass().getDeclaredMethod(this.methodName);
  11. Method method2 = o2.getClass().getDeclaredMethod(this.methodName);
  12. Object obj1 = method1.invoke(o1);
  13. Object obj2 = method2.invoke(o2);
  14. if(methodName.equals("getExpvalue")){
  15. if((double)obj1 > (double)obj2){
  16. return 1;
  17. }else{
  18. return -1;
  19. }
  20. }else{
  21. if((int)obj1 > (int)obj2){
  22. return 1;
  23. }else{
  24. return -1;
  25. }
  26. }
  27. }catch(Exception e){
  28. e.printStackTrace();
  29. }
  30. return 0;
  31. }
  32. }

Quantile Class如下

  1. class Quantile{
  2. private int pos;
  3. private double expvalue;
  4. public Quantile(int pos, double expvalue) {
  5. super();
  6. this.pos = pos;
  7. this.expvalue = expvalue;
  8. }
  9. public int getPos() {
  10. return pos;
  11. }
  12. public double getExpvalue() {
  13. return expvalue;
  14. }
  15. public void setPos(int pos) {
  16. this.pos = pos;
  17. }
  18. public void setExpvalue(double expvalue) {
  19. this.expvalue = expvalue;
  20. }
  21. }

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

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

  1. double avg[] = new double[exp.length];
  2. for(int i = 0 ; i < exp2.length ; i++){
  3. Arrays.sort(n_exp[i], new QuanCompare("getExpvalue"));
  4. for(Quantile t : n_exp[i]){
  5. avg[x] = avg[x]+t.getExpvalue();
  6. if(i == exp2.length-1){
  7. avg[x] = avg[x]/exp2.length;
  8. }
  9. x++;
  10. }
  11. x = 0;
  12. }

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

10,23,44,88

12,54,84,90

36,61,63,95

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

  1. for(int i = 0 ; i < exp2.length ; i++){
  2. for(Quantile t : n_exp[i]){
  3. t.setExpvalue(avg[x]);
  4. x++;
  5. }
  6. x = 0;
  7. }

轉換後,如下

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的排序,值回復原來的位置

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

回復後,如下:

  1. 46.0,19.3,63.7,91.0
  2.  
  3. 46.0,63.7,91.0,19.3
  4.  
  5. 91.0,46.0,19.3,63.7

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

  1. for(int i = 0 ; i < exp2.length ; i++){
  2. for(Quantile t : n_exp[i]){
  3. exp2[i][x] = t.getExpvalue();
  4. x++;
  5. }
  6. x = 0;
  7. }
  8. double new_exp[][] = transpose(exp2);

再來是print new_exp得到,

  1. 46.0,46.0,91.0
  2.  
  3. 19.3,63.7,46.0
  4.  
  5. 63.7,91.0,19.3
  6.  
  7. 91.0,19.3,63.7
  8.  

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

留言