やりたいこと
多次元尺度構成法とは多次元のデータを少ない次元で表現する方法。データ間の距離の関係だけを見て決める。 この多次元尺度構成法のデモをcoffee scriptで実装してみる。
ソースコード
モデル
class Point2D constructor: (x, y, r, vx, vy, label, wnd_width, wnd_height, context) -> @x = x @y = y @r = r @vx = vx @vy = vy @label = label @wnd_width = wnd_width @wnd_height = wnd_height @context = context # # Update poistion with velocity # update_position: -> @x += @vx @y += @vy # # Return x and y with array style # get_data: -> return [@x, @y] # # Calculate distance between points # @param p : Point2D class's instance # calc_distance: (p)-> return Math.pow(@x - p.x, 2)+ Math.pow(@y - p.y, 2) # # Draw point and label to canvas # draw: -> @context.beginPath() @context.strokeStyle = '#00F' @context.fillStyle = 'green' @context.arc(@x, @y, @r, 0, Math.PI * 2, false) @context.fill() @context.stroke() @context.restore() @context.beginPath() @context.font = "18px 'MS Pゴシック'" @context.fillStyle = "red" @context.fillText(@label, @x, @y-(@r*2)) @context.restore() # # Draw point and label with scaling # draw_with_scaling: (max,min) -> prevX = @x prevY = @y @x = (@x - min[0]) / (max[0] - min[0]) * @wnd_width @y = (@y - min[1]) / (max[1] - min[1]) * @wnd_height @draw() @x = prevX @y = prevY exports.Point2D = Point2D
Point2D = require('../src/point2d').Point2D describe "Point2D", -> p1 = undefined p2 = undefined p3 = undefined p4 = undefined p5 = undefined beforeEach -> p1 = new Point2D(0, 0, 0, 0, 0, "", 0, 0, undefined) p2 = new Point2D(100, 200, 30, 2.0, 2.0, "test", 640, 480, undefined) p3 = new Point2D(100, 200, 30, 2.0, 2.0, "test", 640, 480, undefined) p4 = new Point2D(1, 2, 30, 2.0, 2.0, "test", 640, 480, undefined) p5 = new Point2D(2, 4, 30, 2.0, 2.0, "test", 640, 480, undefined) it "should be x and y axis value are 0", -> expect(p1.x).toEqual 0 expect(p1.y).toEqual 0 expect(p1.r).toEqual 0 expect(p1.vx).toEqual 0 expect(p1.vy).toEqual 0 expect(p1.wnd_width).toEqual 0 expect(p1.wnd_height).toEqual 0 expect(p1.label).toEqual "" it "should be x and y axis value are 5, 10", -> expect(p2.x).toEqual 100 expect(p2.y).toEqual 200 expect(p2.r).toEqual 30 expect(p2.vx).toEqual 2.0 expect(p2.vy).toEqual 2.0 expect(p2.wnd_width).toEqual 640 expect(p2.wnd_height).toEqual 480 expect(p2.label).toEqual "test" it "should add velocity for x and y to 102.0 and 202.0", -> p2.update_position() expect(p2.x).toEqual 102.0 expect(p2.y).toEqual 202.0 it "should get x and y value with array style", -> data = p3.get_data() expect(data[0]).toEqual 100.0 expect(data[1]).toEqual 200.0 it "should get 5 as distance", -> expect(p4.calc_distance(p5)).toEqual 5
Multi-Dimension Data
class DataPoint constructor: (data, label, column_name) -> @data = data @label = label @column_name = column_name # # Calculate distance between two points # @param dp : DataPoint's instance # calc_distance: (dp) -> sum = 0.0 for i in [0..@data.length-1] sum += Math.pow(@data[i] - dp.data[i],2) return Math.sqrt(sum) get_data: -> return @data draw: -> return draw_with_scaling: -> return exports.DataPoint = DataPoint
DataPoint = require('../src/datapoint').DataPoint describe "DataPoint", -> p1 = undefined p2 = undefined p3 = undefined p4 = undefined beforeEach -> column_name = ["a","b","c","d","e"] p1 = new DataPoint([1, 2, 3, 4, 5], "test", column_name) p2 = new DataPoint([5, 4, 3, 2, 1], "test", column_name) p3 = new DataPoint([1, 3, 5, 7, 9], "test", column_name) p4 = new DataPoint([3, 5, 2, 4, 6], "test", column_name) it "should be x and y axis value are 5, 10", -> expect(p1.data[0]).toEqual 1 expect(p1.data[1]).toEqual 2 expect(p1.data[2]).toEqual 3 expect(p1.data[3]).toEqual 4 expect(p1.data[4]).toEqual 5 expect(p1.label).toEqual "test" expect(p1.column_name[0]).toEqual "a" expect(p1.column_name[1]).toEqual "b" expect(p1.column_name[2]).toEqual "c" expect(p1.column_name[3]).toEqual "d" expect(p1.column_name[4]).toEqual "e" it "should get x and y value with array style", -> data = p2.get_data() expect(data[0]).toEqual 5 expect(data[1]).toEqual 4 expect(data[2]).toEqual 3 expect(data[3]).toEqual 2 expect(data[4]).toEqual 1 it "should get sqrt(35) as distance", -> expect(p3.calc_distance(p4)).toEqual Math.sqrt(35)
Point Cloud Model
class PointCloud constructor: (points)-> @points = points @distances = [] for i in [0..points.length-1] @distances.push([]) for j in [0..points.length-1] @distances[i].push(0.0) # # Calc distances between points and make distance matrix # calc_distance: -> for i in [0..@points.length-1] for j in [0..@points.length-1] @distances[i][j] = @points[i].calc_distance(@points[j]) # # Draw points # draw_points: -> for i in [0..@points.length-1] @points[i].draw() # # Draw points with scaling # draw_points_with_scaling: -> max = @points[0].get_data() min = @points[0].get_data() for i in [0..@points.length-1] for j in [0..max.length-1] if max[j] > @points[i].get_data()[j] max[j] = @points[i].get_data()[j] if min[j] < @points[i].get_data()[j] min[j] = @points[i].get_data()[j] for i in [0..@points.length-1] @points[i].draw_with_scaling(max, min) exports.PointCloud = PointCloud
PointCloud = require('../src/pointcloud').PointCloud Point2D = require('../src/point2d').Point2D describe "PointCloud", -> pc = undefined points = [] beforeEach -> p1 = new Point2D(1, 2, 30, 2.0, 2.0, "test", 640, 480, undefined) p2 = new Point2D(2, 4, 30, 2.0, 2.0, "test", 640, 480, undefined) p3 = new Point2D(5, 3, 30, 2.0, 2.0, "test", 640, 480, undefined) points.push(p1) points.push(p2) points.push(p3) pc = new PointCloud(points) it "should calculate distances", -> pc.calc_distance() expect(pc.distances[0][0]).toEqual 0 expect(pc.distances[1][1]).toEqual 0 expect(pc.distances[2][2]).toEqual 0 expect(pc.distances[0][1]).toEqual 5 expect(pc.distances[0][2]).toEqual 17 expect(pc.distances[1][0]).toEqual 5 expect(pc.distances[1][2]).toEqual 10 expect(pc.distances[2][0]).toEqual 17 expect(pc.distances[2][1]).toEqual 10
main
Point2D = require('../src/point2d').Point2D DataPoint = require('../src/datapoint').DataPoint PointCloud = require('../src/pointcloud').PointCloud main = ()-> # Draw area canvas = document.getElementById('canvas') # Canvas interface context = canvas.getContext('2d') # Make blog name array keys = [] for key of blog_data keys.push(key) num_points = keys.length # Make data point's cloud data_points = [] for i in [0..num_points-1] data_points.push(new DataPoint(blog_data[keys[i]], keys[i], column_name)) realpc = new PointCloud(data_points) # Make 2d(for canvas) point's cloud points = [] for i in [0..num_points-1] x = canvas.width /2 + Math.floor(Math.random() * 200) - 100 y = canvas.height /2 + Math.floor(Math.random() * 200) - 100 vx = 0.0 vy = 0.0 points.push new Point2D(x, y, 2, vx, vy, keys[i], canvas.width, canvas.height, context) fakepc = new PointCloud(points) fakepc.draw_points_with_scaling() # Calculate realdist realpc.calc_distance() # Calculate initial fakedist fakepc.calc_distance() # Error tmp lasterror = 0.0 # Learning Rate rate = 0.0001 # Done flg of calculation endflg = false mainloop = ()-> context.save() context.beginPath() context.clearRect(0, 0, canvas.width, canvas.height) context.restore() if endflg fakepc.draw_points_with_scaling() else totalerror = 0 # Calculate initial fakedist fakepc.calc_distance() # Calculate velocity of canvas point for i in [0..num_points-1] for j in [0..num_points-1] if i==j continue errorterm = (fakepc.distances[j][i] - realpc.distances[j][i])/realpc.distances[j][i] points[i].vx += ((points[i].x - points[j].x)/fakepc.distances[j][i])*errorterm points[i].vy += ((points[i].y - points[j].y)/fakepc.distances[j][i])*errorterm totalerror += Math.abs(errorterm) # Check local minimum console.log(totalerror) if lasterror > 1.0 and lasterror < totalerror endflg = true else # Save totalerror lasterror = totalerror # Update canvas point's position for i in [0..num_points-1] points[i].x -= rate * points[i].vx points[i].y -= rate * points[i].vy # Draw points fakepc.draw_points_with_scaling() # Do again after 30 millisecond setTimeout(mainloop, 30) mainloop() window.onload = main