Categories: Uncategorized

[CoffeeScript]Multi-Dimesional-Scaling

What I want to do

Multi-Dimension-Scaling is how to explain high dimension data as low dimension data.
This method focus on distances between data. Try to implement MDS’s demo with cofee script.

Source Code

Model

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

Result

zuqqhi2