编辑代码

# AUthor:Housyou 学院:地空学院

LOGICAL_0 = 1e-10  # 逻辑0值
THRESHOLD = 0.85  # 主成分分析的累计百分比阈值

class Array  # 为内置的数组Array类添加几个方法
  def *(array)  # 数组是个矩阵时的矩阵乘法
    res = nil  # 将要返回的结果,新的矩阵
    if self[0].size == array.size && !array.empty?  # 如果矩阵形状可以进行乘法
      dim = array[0].size  # 后矩阵的列数
      res = Array.new(self.size){Array.new(dim)}
      self.each_with_index do |line,i|  #对前矩阵每行进行遍历,line是每一行,i是索引
        dim.times do |j|  # 对后矩阵每一列进行遍历,j是索引
          value = 0  # 行乘列的结果
          line.each_with_index do |a,k|  # a是前矩阵一行中的每个元素,k是索引
            value += a * array[k][j]  # 行乘列累计求和
          end
          res[i][j] = value  # 将结果保存
        end
      end
    end
    res  # 将矩阵乘法的结果返回,Ruby会自动将最后一次执行的结果作为返回值
  end

  def trans  # 转置
    res = Array.new(self[0].size){Array.new(self.size)}  # 结果的行数和列数对换
    self.each_with_index do |line,i|  # 遍历赋值
      line.each_with_index do |num,j|
        res[j][i] = num
      end
    end
    res  # 返回结果
  end

  def sort_zip(array)  # 对自身进行排序,顺带将另一个数组array按自己的顺序排序
    a1 = self.clone  # 拷贝副本,防止改变自身结果
    a2 = array.clone
    for i in 1...a1.size  # 直接插入排序,因为规模比较小所以不需要用高效的排序算法
      key = a1[i]
      ano = a2[i]
      j = i - 1
      while j >= 0 && yield(key,a1[j])  # yield是调用方法时传入的排序原则
        a1[j + 1] = a1[j]
        a2[j + 1] = a2[j]
        j -= 1
      end
      a1[j + 1] = key
      a2[j + 1] = ano
    end
    {:self=>a1,:other=>a2}  # 排序的结果用散列记录,:self键返回自身排序结果,另一个array排序的结果的键是:other
  end

  def formal  # 矩阵转化为标准化的字符串形式
    res = ""
    self.each do |line|  # 遍历每行
      res += "|"
      line.each do |num|  # 遍历每列
        res += num.round(3).to_s + "\t"
      end
      res += "|\n"
    end
    res
  end

  def find_max  # 找到绝对值最大的元素,返回值和索引
    max_num = 0  # 记录最大的值
    pos = nil
    self.each_with_index do |line,i|
      line.each_with_index do |num,j|
        if i != j
          abs = num.abs
          if abs > max_num
            pos = [i,j]
            max_num = abs
          end
        end
      end
    end
    {value:max_num,i:pos[0],j:pos[1]}
  end
end

def standarlize(address)  # 输出标准化的数据

  def get_ori_data(address)  # 读取原始数据
    res = Array.new  # 保存数据的矩阵数组
    File.open(address) do |file|  # 读取文件
      file.each_line do |line|  # 按行读取
        res.push(line.chomp.split("\t").map {|i| i.to_f})  # 切割后转换为数组,保存数组
      end
    end
    res  # 返回结果
  end

  def standarlize_a_line(array)  # 对一组x进行标准化
    sum,sum2 = 0.0,0.0  # 累计和、累计平方和
    array.each do |num|  # 计算累计和和累计平方和
      sum += num
      sum2 += num * num
    end
    mean = sum / array.size  # 平均数
    s = Math.sqrt((sum2 - sum * mean) / (array.size - 1))  # 标准差
    if s > 1e-10  # 标准差不为0,就输出标准化的数据
      array.map do |num|  # map函数直接对每个数进行替换
        ((num - mean) / s)
      end
    else  # 如果标准差为0
      array.map do |num|
        0  # 平均数也是0,所以每个数都是0
      end
    end
  end

  (get_ori_data(address).trans).map{|line| standarlize_a_line(line)}  # 对整个原始数据进行标准化,返回结果
end

def r_array(data)  # 从标准化数据计算相关系数矩阵
  dim = data.size
  free_degree = data[0].size - 1
  res = Array.new(dim){Array.new(dim)}  # 记录结果
  dim.times do |i|  # 遍历每组x
    dim.times do |j|  # 再次遍历每组x
      r = 1  # 如果 i 和 j 相等,相关系数就是1
      if i > j  # 如果是下半矩阵,直接复制对称位置的值
        r = res[j][i]
      elsif i < j  # 如果是上半矩阵就计算相关系数
        r = 0
        data[i].zip(data[j]) do |a,b|  # 遍历同一个索引对应的两组x的数
          r += a * b  # 计算乘积和
        end
        r /= free_degree  # 相关系数还要除以自由度,因为之前计算方差时是用自由度计算的
      end
      res[i][j] = r  # 将相关系数赋到对应的位置
    end
  end
  res
end

def jacobi(r)  # 雅可比法计算特征向量和特征值
  def get_i(n)  # 获得一个 n 阶单位矩阵
    array = Array.new(n){Array.new(n){0}}  # 对角线外都是0
    n.times {|k| array[k][k] = 1}  # 对角线上是1
    array  # 返回 array
  end
  dim = r.size  # 是方阵
  t = get_i(dim)
  loop do  # 不算到非对角元素全为0就不停止循环
    res = r.find_max  # 找到最大的元素及其索引
    if res[:value] > LOGICAL_0  # 如果非对角元素有不为0的
      i,j = res[:i],res[:j]
      delta = r[i][i] - r[j][j]
      theta = delta.abs < LOGICAL_0 ? -Math::PI/4 : Math.atan(2*r[i][j]/delta)/2  # 取 atan ∞ = -90°
      new_t = get_i(dim)  # 本次迭代的变换矩阵
      cos = Math.cos(theta)
      sin = Math.sin(theta)
      new_t[i][i],new_t[j][j] = cos,cos
      new_t[i][j],new_t[j][i] = -sin,sin
      r = new_t.trans*r*new_t
      r[i][j] = 0  # 为了提高精确度,直接令消去的位置为0
      t *= new_t  # 连乘最后争取获得特征向量
    else  # 满足条件后结束循环
      values = Array.new(dim)  # 记录特征值
      dim.times do |i|
        values[i] = r[i][i]
      end
      return values.sort_zip(t.trans){|a,b| a > b}  # 按特征值对特征值和特征向量排序后返回
    end
  end
end

def contribution(values)  # 计算贡献率
  n = values.size
  res = Array.new(n)
  sum = 0
  values.each {|v| sum += v}  # 计算特征值之和
  n.times {|i| res[i] = values[i] / sum}  # 计算贡献率
  res  # 返回结果
end

def sum_contribution(con)  # 计算累计贡献率及累计贡献率超过阈值时的主成分数量
  thres_index = 0  # 阈值对应的主成分索引
  res = Array.new(con.size){0}  # 计算结果初始化为0
  con.each_with_index do |rate,index|  # 遍历特征值
    res[index] = res[index - 1] + rate  # 如果是第一个特征值,会在最后一个特征值的累计贡献率(目前是0)基础上相加
    if thres_index == 0 && res[index] > THRESHOLD # 超过阈值时记录索引
      thres_index = index
    end
  end
  {:threshold=>thres_index,sum_con:res}  # 返回索引和累计贡献率
end

def load_array(vectors,values)  # 计算载荷矩阵
  dim = [vectors.size,vectors[0].size]
  res = Array.new(dim[1]){Array.new(dim[0])}
  dim[1].times do |i|
    dim[0].times do |j|
      res[i][j] = Math.sqrt(values[j]) * vectors[j][i]
    end
  end
  res
end

def scores(vectors,data)  # 计算主成分得分矩阵
  (vectors * data).trans
end

def analysis(scores_array,num_of_component=1,amount=4,highest=true)  # 分析主成分得分,返回流域索引(从1开始)
  # scores_array是得分矩阵,num_of_component是第几主成分,amount是最高或最低的选取数量,highest判断是否选取最高的几个
  nc = num_of_component - 1
  dim = scores_array.size
  order = Array.new(dim)  # 排序前的索引
  dim.times {|i| order[i] = i + 1}  # 就是原顺序,再加上从1开始计数
  order = (scores_array.sort_zip(order){|a,b| a[nc] > b[nc]})[:other]  # 按主成分得分排序后取order的排序结果
  res = Array.new(amount)  # 待返回的流域索引(从1开始)
  if highest  # 若想最高的,就从头添加
    amount.times do |i|
      res[i] = order[i]
    end
  else  # 反之则从尾部开始添加
    amount.times do |i|
      res[i] = order[-i-1]
    end
  end
  res
end

def main
  data = standarlize("input.txt")  # 获得标准化处理后的数据
  cca = r_array(data)  # 相关系数矩阵

  puts "相关系数矩阵"
  puts cca.formal

  jac = jacobi(cca)
  con = contribution(jac[:self])
  sc = sum_contribution(con)

  puts "特征值及其百分率"
  puts "主成分\t特征值\t百分数\t累计百分数"
  jac[:self].each_with_index do |v,i|
    puts  "#{i+1}\t#{v.round(3)}\t#{con[i].round(3)}\t#{sc[:sum_con][i].round(4)}"
  end

  vectors = jac[:other][0..sc[:threshold]]
  puts "主成分载荷矩阵"
  puts load_array(vectors,jac[:self]).formal

  s = scores(jac[:other],data)
  puts "第一主成分得分最高的流域"
  res = analysis(s)
  res.each do |i|
    print i.to_s + "\t"
  end
  print "\n"
  res.each do |i|
    print s[i - 1][0].round(3).to_s + "\t"
  end
  print "\n"

  puts "第一主成分得分最低的流域"
  res = analysis(s,1,4,false)
  res.each do |i|
    print i.to_s + "\t"
  end
  print "\n"
  res.each do |i|
    print s[i - 1][0].round(3).to_s + "\t"
  end
end

main