会社を立ち上げ、サイトリニューアルしました

By Kousuke Takeuchi on 22 August 2018 | Comments (0)

この度8月から株式会社Whitepaper(ホワイトペーパー)を代表取締役として設立することになり、合わせて学生時代から書いていたブログやQiitaの記事 を移行し、本サイトをリニューアルオープン致しました。 Whitepaperでは再発明したQRコード(KVSコードと呼んでいます)を含めたITソリューションを活用して、施設へのチェックインや店舗での決済など、お客様のスムーズなイベント体験を提供するためのサービスを各業者様と提携して展開してまいります。

関連して、本ブログではWhitepaperでの分析・開発関連の記事を投稿していこうと思います。 引き続きご支援ご厚情を賜りますよう宜しくお願い申し上げます。

竹内宏佑 - Whitepaper Inc. 代表取締役


iOSの開発でお気に入りのライブラリN選

By Kousuke Takeuchi on 18 June 2015 | Comments (0)

殿堂入り

Realm

Core Dataなんて最後に使ったのはいつだろうか。。。

Alamofire SwiftyJSON

通信ライブラリAlamofireのブロック構文に、JSONをより扱いやすくするSwiftyJSONをぶっこむ。 最近SwiftyJSONの仕様が変わってイマイチになったが、Swiftバージョン2.0でguard構文が追加されるので、guardで宣言するときに使いやすくなるかも。

SDWebImage

みんな知ってる画像キャッシュライブラリ。

UI

paper switch

スイッチのON/OFFで色をアニメーション付きで変更する。

paper

SCLAlertView

フラットなアラート sclalert

LTMorphingLabel

文字をモーフィング(アニメーション)させる ltmorphing

MMPopLabel

ポップラベル。Slackの立ちあげ時とかに出てくるアプリケーションのチュートリアルとかに使う。(まずはここを押してみよう!的な) poplabel

RKNotificationHub

FB風の通知バッジ。当然アプリ内の通知で使っている。 上のMMPopLabelと併用すると、FB風のおしらせ機能のUIを作成できた。 notbadge

CTCheckbox

規約踏ませるときに使用したチェックボックス。 checkbox

PNChart

グラフ描画ライブラリ。アニメーションが無いのが残念。 pnchart

TTTAttributedLabel

FBやSlackのメッセンジャーみたいに、テキスト内に出てきたURLに自動でリンクを付ける。

BubbleTransition

画面遷移をしゃれおつにする。View内のコンポーネントに対してアニメーションを付加するpaper switchとは違い、セグエに対してアニメーションの機能を与えてくれる。 bubble

What’s New

バージョンアップの自慢。大きく機能追加した場合は表示させて鼻高々になる日々もいいかも。

what's new

イントロ

WSCoachMarksView

コーチマーク。まずはここを触って、次はここを動かして、、、みたいにユーザーを導く。 coach2 コーチマークを自分で実装したい人はこちら

CMPopTipView

poptip ツールチップチュートリアル。UIのMMPopLabelと似ている。こちらはユーザーへのイントロでよく使用する。

COSTouchVisualizer

タッチの方法を視覚化。ユーザーもここまで教えてあげればアプリの使い方がわかりやすいかも。 cos

ナビゲーション

RAMAnimatedTabBarController

アニメーションするタブバー。あんまり使う場面なかった。 animatedtab

TLYShyNavBar

シャイなナビゲーション。かわいい。 tlyshy

iOS Slide Menu

みんなだいすきスライドメニュー。 slie menu

Persei

おしゃれすぎて使い道わからん persei

タブメニューをスライドで遷移できるよ。 pagemenu

RESideMenu

パララックスの遺伝子組み換えでスライドメニューの限界を超えた。 residemenu

REFrostedViewController

ぼかしの入ったナビゲーション REfrosted

MotionBlur

モーション早すぎ漏れた。 motiion

コレクション

PSCollectionView

Piterestみたいなコレクションビュー pscollection

SWTableViewCell

セルのスライド機能。メールアプリによくあるやつ。 swtable

DZNEmptyDataSet

テーブルにデータが無い時の画面。意外と超便利。 DZN

フォーム

XLForm

記入フォーム form1

FXForms

記入フォーム2。こっちのほうがよく使う。 form2

マテリアルデザイン

MaterialKit

マテリアルデザインの標準キット。 materialkit

ZFRippleButton

水滴のようなアニメーションのボタン ripple

デバイストラッキング

Mixpanel

ユーザートラッキング

Odinmobile

Mixpanelとセットで使うオープンデバイスIDのライブラリ

ソーシャル

SimpleAuth

Twitter, Facebook, LinkedIn..など何でも外部認証できる

AAShareBubbles

シェアバブル。Evernoteにもあった気がする。 sharebubble

Lock.ReactNative

外部ログイン & タッチID kick

LineKit

ラインで送る。

appirater

レビューを促す嫌われ者。この世から消えて欲しい。

通信

Transporter

バックグランド通信タスク。

TNSexyImageUploadProgress

セクシーすぎて鼻血出た。 sexy

FMAssetStream

ナビゲーションにプログレス FMAsset

SwiftSpinner

近未来的プログレス。 spinner

位置情報

LocationManager

現在情報を一度だけ取得。

MAPS ON MOBILE

マップキット。webダッシュボードまでついてるよ。 maponmobile

AV

RSBarcodes

軽量バーコードリーダー。

RSKImageCropper

画像の切り抜き cropper

PhotoTweaks

画像の切り抜き2。こっちのほうが多機能。 tweaks

ImageScout

低コスト画像ダウンロード

有名企業作品

Facebook

Instagram (デザインと画像処理が強い)

LinkedIn (謎)

Tumblr

IFTTT

Dropbox

  • zxcvbn ios パスワードの強度を教えてくれる

Path

Vine

Slackで使われてるらしい

(参考) slack iosアプリで使われているライブラリ群をとりあえず眺めてみる(2015年2月時点)

(おまけ) サービス編

NewRelic

クラッシュレポートとかHTTPエラーとかユーザーアクティビティとか色々解析できるプラットフォーム。

【利用箇所】めっちゃ便利。NSLogとかも保存してくれたらいいのになー。 techcrunch ios

Fabric

もともとはCrashlyticsというアプリのクラッシュレポートのツールだったのを、Twitterが買収して、広告配信プラットフォームやTwitter APIとかを統合させたもの。Fabricを一括りで見ると大量の機能が詰まりすぎてカオス。

【利用箇所】 Crashlytics : クラッシュした時に原因を知らせてくれたり、ユーザーのアクティビティの解析をしてくれたりする。自分はNewRelicに満足しているのでクラッシュレポートは使わないが、実はTestFlightDeployGateのように、他の端末にアプリを配信してくれたりするので便利。

Crashレポートやアプリの配信以外にも、Twitterや広告周りのこともやってくれるので、もはやFabricなしでiOSの開発は考えられない。

techcrunch

Parse

dashboard モバイルバックエンドの代表。サーバーサイドで複雑な処理がない場合は、Parseを使えば大体のアプリケーションはモバイルプログラムでこと済んでしまう。

【利用箇所】サーバーサイド機能

  • 会員登録
  • ユーザーセッション
  • データ保存(画像もいける)
  • ユーザーのデータ解析

確率論の小話 (2)

By Kousuke Takeuchi on 18 December 2014 | Comments (0)

先日書いたベイズ統計と古典統計の違いについてのシミュレーションプログラムを作成したので掲載します 確率論の小話

1. カードのクラスを定義

class Card
    attr_reader :suite
    attr_reader :rank

    def initialize(suite, rank)
        @suite = suite
        @rank = rank
    end

    def is_dia?
        @suite == 'dia'
    end
end

2. 52枚のカードの集合(デッキ)を定義

class Deck
    def initialize
        @deck = []
    end

    def add(card)
        @deck << card
    end

    def shuffle!
        @deck.shuffle!
    end

    def draw
        @deck.pop()
    end
end

3. ゲームの一連の流れをクラスに定義

class Game
    @@suites = ['spade', 'heart', 'dia', 'club']
    @@ranks = [1..13]

    attr_accessor :card_in_box

    def initialize
        @deck = Deck.new
        @@suites.each do |s|
            @@ranks.each do |r|
                card = Card.new(s, r)
                @deck.add(card)
            end
        end

        @deck.shuffle!
        @card_in_box = @deck.draw
    end

    def draw_three_cards
        3.times.map{|n| @deck.draw}
    end
end

4. シミュレーションの実行

# 箱にダイヤが含まれる単純な確率
games = 1000
count = 0
games.times do |g|
    game = Game.new
    card_in_box = game.card_in_box
    if card_in_box.is_dia?
        count += 1
    end
end

printf("Frequency principle : \t%.4f", count.quo(games))

rest_games = games
count = 0
while rest_games > 0 do
    game = Game.new
    three_cards = game.draw_three_cards

        # 引いた3枚のカードが全てダイヤの時
    if three_cards.all?{ |card| card.is_dia? }
        limit -= 1
                # 箱に入っているカードもダイヤかどうか
        if box_in_card.is_dia?
            count += 1
        end
    end
end

printf("Bayes principle : \t%.4", count.quo(games))

確率論の小話

By Kousuke Takeuchi on 10 December 2014 | Comments (0)

7年前に2chで話題になった、確率に関する問題(早稲田の入試問題?)の解説にいいものが見当たらなかったので、自分なりにまとめてみる。

よく話題になる確率の問題を集めてみる

問) ジョーカーを除いたトランプ52枚の中から1枚のカードを抜き出し、 表を見ないで箱の中にしまった。 そして、残りのカードをよく切ってから3枚抜き出したところ、 3枚ともダイアであった。 このとき、箱の中のカードがダイヤである確率はいくらか。

現代の確率論について

そもそも現代の確率の考え方は、

  1. 無限に実験 (厳密には試行という) を重ねて、その結果から導出する頻度主義
  2. ある仮説をたて、その仮説が起こる確率を計算し、その後観測を行い、得られた観測結果から仮説の起こる確率を更新するベイズ主義

の2つが主流である

頻度主義からの考察

1の考え方と言うのは、(実際には不可能だが) 何度も試行を繰り返すことができることに対して確率を割り当てることであり、今回のトランプ問題も何度も試行を繰り返せるものだと考えれば、この頻度主義にそって確率を計算すればよい。 したがって、トランプのカードを取り出して箱に入れるという作業を何度も繰り返せば、総数52枚のカードから総数12枚あるダイヤのカードが取り出される確率に一致するため、13/52 = 1/4 という計算になる。(不安な方は、実際に実験してみればよい)

ベイズ主義からの考察

この問題の結果を大きく分けるのは、「3枚のダイヤが出た」というデータをどのように扱うかである。もしこのデータを考慮して確率を求めるなら、ベイズ主義が上手く当てはまる。 ベイズ主義による確率は以下の公式によって計算される。

p( H | D ) = p( H ) × p( D | H ) ÷ p( D )

H Hypothes : ある仮説
D Data : 観測データ
p( H D )というのは、Dというデータを得られたときにHという仮説が起こる確率であり、今回は
H → 箱の中身はダイヤである
D → ダイヤを3枚引いた

と適応できる。ここで右辺の3つの項について解説すると

p( H ) → ダイヤを3枚引いたことは無視して、Hが起こる確率。(事前確率)
p( D | H ) → もし箱の中身はダイヤだったら、その後ダイヤを3枚引く確率はどのくらいとなるか 。(尤度)
p( D ) → どのような仮説を立てたかに関係なく、ダイヤを3枚引く確率。(正規化定数)

となる。実際にこの3項目を計算すると、

p( H ) 52枚のカードから13枚あるダイヤが取り出される確率なので1/4
p( D | H ) 箱の中身がダイヤなら、残り総数51枚のカードの中からダイヤが余り12枚中3枚取り出される確率は、(12 3) / (51 3) = 44/4165

ただし、(n m) = n! / { (n-m)! × m! } とする。

p( D ) 箱の中身に関係なく、ダイヤが3枚出る確率は、(13 3) / (52 3) = 11/850

長くなったが以上より、p( H | D ) = 1/4 × 44/4165 ÷ 11/850 = 10/49 となる( ≒ 0.20 < 1/4 ) つまり、「3枚もダイヤが出たため、箱にまでダイヤが入ってることは珍しいことになり、頻度主義の確率よりも低い確率と計算された。」と解釈できる。

ではどちらを採択すべきか

頻度主義は「実験 (試行)」を繰り返すことにより計算できるため、客観的に説明することができる。例えば今回の問題も、1000兆回くりかえせば必ず確率は1/4になるため、「ほら、4回中1回はダイヤになるでしょ?」と証明できるわけだ。 対するベイズ主義からの視点で考えると、今回はあくまで「ダイヤが3枚出てきた」という唯一の観測結果だけで考察しているため、今後実験を繰り返して確率が1/4だったといわれても関係ないのである。 まとめると、

  • 頻度主義は「客観的で証明可能」な確率を導き出し、
  • ベイズ主義は「主観的で現在所有しているデータのみで計算可能」な確率を導き出す。

今回の問題は、どちらの主義も採択可能なため、問題文中で非常に繊細な状況解説が必要となる。 例えば、「問題のような実験を繰り返しており、最終的にどのような確率になりうるか」といえば頻度主義を採択し、「カジノで有り金を全部かけてカードゲームをしている」のならばベイズ主義を採択すべきである。

個人的な意見

ここからはあくまで個人的な意見になるが、今回の問題は大学の入試試験で出題されたものであるため、以下のように大学の視点に立って主義選択をするのが大学生という身分として健全な思考なのかもしれない。

国公立大学の入試 国民に公表できる客観的な考え方が望まれているため、頻度主義を利用する。
私立大学の入試 私(ワタクシ)の視点、つまり主観的な考え方が望ましいため、ベイズ主義を採択する。

追記

では頻度主義とベイズ主義では違う確率になるのかというと、実は「最終的」には一致する。 今回は「ダイヤが3枚出た」というデータのみでベイズ確率を計算したため頻度主義とは異なる結果になったが、3枚のダイヤをもとに戻してもう一度3枚引いてみて、例えば「クラブが2枚、ハートが1枚出た」というデータが得られたならば、ベイズの公式にそって確率を更新し、このようにデータをとって確率を更新する作業をずっと繰り返せば、結果は1/4に収まるのである。

したがって頻度主義とベイズ主義での確率は「最終的」には一致するため、ベイズ確率は「頻度主義で行われるたくさんの試行の過程の中で、一時的に算出される確率」と考えるのが望ましい。つまり、頻度主義とベイズ主義は相反するものではなく、頻度主義を分けてみるとベイズ主義が隠れているといった解釈をするのが良いと思う。


Rails + Redis + AWSでPV数を保存

By Kousuke Takeuchi on 03 May 2014 | Comments (0)

AWS上で動くRailsアプリのページビューを保存する方法について書いていきます。 今回は基本的なことのみ書きますが、例えばブログのデイリーPVランキング、個別ページのPV数の推移グラフの作成などに応用出来ます。

RedisをAWSに追加

AWSには、 ElastiCache というRedisやMemcachedなどのNoSQL専用のサービスがあります。 新規Redisインスタンスの作成手順は簡単なので、他のサイトの記事を参考にしてRedisを追加してみてください。

僕は下のサイトを参考にしました Using AWS ElastiCache for Redis With AWS OpsWorks

RedisをAWSに追加して、インスタンスの詳細画面に移動してください。

elasticache-instance-details

RailsでRedisを使う際に、画面のPortとEndpointを使用するので、Railsの環境変数に追加しておきましょう。

# config/environments/production.rb
ENV["REDIS"] = "xxxx.xxxx.chache.amazonaws.com:6379" # Endpoint + Port

AWSで追加したRedisは、AWSのRailsインスタンスからしか接続できません。従って、ローカルからはElastiCacheのRedisに接続することが出来ないので、もしAWSではなくローカルでRedisを使いたい場合は、以下のようにRedisをインストール、設定してください。

1. Redisのインストール (Mac)

$ brew install redis
$ redis-server

2. Railsの環境変数に追加

ENV["REDIS"] = "localhost:6379"

RailsでRedisを使うための設定

RailsでRedisを扱うためにgemを追加します

# Gemfile
gem 'redis'
$ bundle install

initializerフォルダに、Redisの初期設定を追加します。

# config/initializers/redis.rb
require 'redis'

uri = URI.parse(ENV["REDIS"])
REDIS = Redis.new(host: uri.host, port: uri.port)

これで、REDISという変数をRailsから呼び出すことによって、Redisを使えるようになりました。

RedisにPV数を保存

例えば、CMSアプリケーション(ブログなど)で使う場合を考えます。 Postモデルがあって、PostsControllerからページの操作をするとします。

PostControllerのshowメソッドで、個別ページを表示するなら、以下のコードを追加します。

# controllers/posts_controller.rb
def show
 @post = Post.find(params[:id])
 ...

 REDIS.incr "posts/daily/#{Date.today.to_s}/#{@post.id}"
end

REDIS.incrは、数値に1を足して保存するメソッドです。 例えば、2014年4月1日に、id=100の記事が初めて読まれたとすると、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100 に「1 (= 0 + 1)」が保存されます。

もう一度読まれた場合は、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100 に「2 (= 1 + 1)」が保存されます。

さらにもう一度読まれた場合には、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100 に「3 (= 2 + 1)」が保存されます。

このように、ページビュー毎にID別のPV数がRedisに保存されます。

RedisからPV数を取得

REDIS.get "posts/daily/#{Date.today.to_s}/#{@post.id}"
# => 今日のPV数

REDIS.get "posts/daily/#{Date.yesterday.to_s}/#{@post.id}"
# => 昨日のPV数

例えば、全ての記事に関してPV数を取得すれば、毎日の人気記事ランキングを作成出来ます。

Ex.)

@posts = Post.all
@daily_pageviews = Hash.new
today = Date.today.to_s

# 個別記事のPV数を取り出す
@posts.each do |post|
 @daily_pageviews[@post.id] = REDIS.get "posts/daily/#{today}/#{post.id}"
end

# PV数のソーティング
@daily_pageviews.sort_by{|k, v| v}

#上位10個の記事を返す
@top10_pages = @daily_pageviews[0..10]

(追記) より良い方法

Redisにはソート済みセットという型が用意されています。もし月間PVランキングみたいなものを実装したいのであれば、この型を使うと自動的にランキングが出力されるので便利です。

ソート済みセットの関数「zincrby」は、「キー・数値・メンバー」を引数とし、あるメンバーにキーが存在すれば数値分だけ増やし、キーが存在しなければ数値をセットします。

例えば、「2015/1/1の記事」というメンバーに「id=10の記事」というキーがあって、「PV数が100件」といった数値があると考えると、PV数のランキングは以下のように実装できます。

def show()
  @post = Post.find(params[:id])
  ...
  # ex.) REDIS.zincrby "posts/daily/2015-01-01", "1", "10"
  #      2015年1月1日にid=10の記事の総PV数を1増やす
  REDIS.zincrby "posts/daily/#{Date.today.to_s}", 1, "#{@post.id}"
end

zrevrange関数は、メンバーの降順にソートされた数値の中から、指定された範囲の数値を持つキーを取得します。昇順の場合はzrange関数を使います。 このzrevrangeを使うと、PV数のランキングデータを取得するのも2行で済みます。

# PV数1位から20位までの記事を取得
ids = REDIS.zrevrange "posts/dayly/#{Date.today.to_s}", 0, 19
@posts = Post.where(id: ids)

あなたにオススメの〜的なサービスを実装する(アルゴリズム改良版)

By Kousuke Takeuchi on 19 December 2013 | Comments (0)

前回までは、数式通りにゴリゴリとレコメンド行列を作成していきましたが、前回のプログラムでは計算量がO(m^3)になってしまいます。(mはユーザーデータの量)

したがって、ユーザーが1000人、2000人と増えていくに連れて、一気にプログラムの速度が落ちます。 今回は、前回と同様の計算を行う上でより効率的なプログラムを紹介します。 このプログラムでは、理論的に計算量がC(mn)になります。(nはアイテムの個数)

ただし、今回はユーザーが2つのアイテムだけ購入する前提で作成しました。

まずは、前回のプログラムの実行時間を計測できるようにします。

import numpy as np
from random import randint
import time

def generateTable(m, n):
    a = np.zeros([m, n])
    for row in range(0, m):
        col1 = int(randint(0, n - 1))
        a[row][col1] = 1

        col2 = int(randint(0, n - 1))
        while col2 == col1:
            col2 = int(randint(0, n - 1))
        a[row][col2] = 1
    return a

start = time.clock()

m, n = 1000, 42
A = generateTable(m, n)

# create association table
fav_table = []
for i in range(len(A[0])):
        fav_row = []
        for j in range(len(A[0])):
                fav_row.append([])
                fav_row[j] = 0
                for k in range(len(A)):
                        fav_row[j] += A[k][i] * A[k][j]
        fav_table.append(fav_row)

# calculate association rate from table
transposed = [[row[i] for row in fav_table] for i in range(len(A[0]))]
def add(x, y): return x + y
sum_table = [reduce(add, transposed[i]) for i in range(len(transposed))]

for i in range(len(fav_table)):
        for j in range(len(fav_table[0])):
                fav_table[i][j] /= float(sum_table[j])

end = time.clock()
time = end-start
time = str(round(time, 3))
print 'Older Process: ' + time + 'second'

今回は、ユーザーのアイテム購入データが1000件、アイテム数が42個として計算します。

これを実行した結果、4.3秒ほどの時間がかかりました。

続いて新しいプログラムを紹介します。

import numpy as np
from random import randint
import time

def generateTable(m, n):
    a = np.zeros([m, n])
    for row in range(0, m):
        col1 = int(randint(0, n - 1))
        a[row][col1] = 1

        col2 = int(randint(0, n - 1))
        while col2 == col1:
            col2 = int(randint(0, n - 1))
        a[row][col2] = 1
    return a

def createRawAssociate(a, m, n):
    b = np.zeros([n, n])
    for k in range(0, m):
        findAssociate(a, b, n, k)
    b_tran = np.transpose(b)
    return b + b_tran

def findAssociate(a, b, n, k):
    col1, col2 = 0, 0
    for j in range(0, n):
        if a[k][j]:
            col1 = j
            break
    for l in range(col1 + 1, n):
        if a[k][l]:
            col2 = l
            break
    b[col1][col2] += 1

def totalVector(b, n):
    c = np.zeros(n)
    for j in range(0, n):
        for k in range(0, n):
            c[j] += b[k][j]
    return c

def createAssociateTable(b, c, n):
    d = np.zeros([n, n])
    for i in range(0, n):
        for j in range(0, n):
            if c[j]: d[i][j] = round(b[i][j] / c[j], 2)
    return d

start = time.clock()

m, n = 1000, 42
A = generateTable(m, n)
B = createRawAssociate(A, m, n)
C = totalVector(B, n)
D = createAssociateTable(B, C, n)

end = time.clock()
time = end-start
time = str(round(time, 3))
print 'Process: ' + time + 'second'

リファクタリングが出来ていないので、少し長ったるいプログラムに見えますが、いざ実行してみると、結果は0.045秒で実行できました!

つまり、たった1000件のユーザーデータを扱う場合でも、今回の改良版アルゴリズムでは100倍の速度で実行が可能になります。


あなたにオススメの〜的なサービスを実装する(数式編)

By Kousuke Takeuchi on 01 December 2013 | Comments (0)

前回の解析では、Pythonを知らない人にとっては分かり難いプログラムだったと思うので、レコメンドサービスで使用するアソシエーション解析の数学的理論について書こうと思います。 この公式は、こちらのサイトの内容の前半部分を元に自分で作成した公式になりますので、確実性は保証できませんが、簡単なアソシエション解析を行う際には問題ないと思います。

第2回 「ある商品といっしょによく売れる商品は何か?」を見つけるには ~マーケット・バスケット分析の考え方:Mahoutで体感する機械学習の実践|gihyo.jp … 技術評論社

1. 顧客の商品購入データの行列Aから、商品の組み合わせ購入行列Bを作成する。

として、以下の公式で計算する

ただし、より、

2. 組み合わせ購入比率行列の計算

とすると、求める組合せ購入比率行列Cは

となる。

これらの公式をプログラムにしたのが、前回ご紹介したPythonのプログラムになります。


あなたにオススメの〜的なサービスを実装する

By Kousuke Takeuchi on 01 December 2013 | Comments (0)

AmazonやFacebookに代表されるように、「あなたにオススメの〜」みたいなサービスを構築する方法について考えてみます。いわゆるレコメンド・サービスのためのアソシエーション解析の一環ですね。

まずは解析するデータを自動で作成するために、ExcelでVBAを書きます。

Sub setRnd()
  For i = 2 To 31
    For j = 2 To 17
      Worksheets("Sheet1").Cells(i, j).Value = Round(Rnd())
    Next
  Next
End Sub

作成したデータがこちら。

例えば(A1, a3)は1となっていますが、これはA1というユーザーがa3という商品を購入した。もしくは、A1というユーザーがa3と友達である。みたいな解釈をしてください。 0の場合は、繋がりが無いといった感じです。

さて、このデータから、例えば「a2とa7は、よく購入される組み合わせだ!」みたいなことを数値的に解析してみましょう。

まずはこのデータをcvsファイルに変換し、pythonで解析できるようにします。

次に、この「生」のデータから、組み合わせ表を作成します。 とりあえずプログラムを見て行きましょう。

1. csvファイルを読み込む

import sys
import types

with open('Worlbook1.csv', 'r') as csv:
        for line in csv:
                data = line.strip().split(",")

# fix csv to create data table
matrix = []
row = []
for l in data:
        try:
                num = int(l)
        except ValueError:
                matrix.append(row)
                row = []
        else:
                row.append(num)
del matrix[0]

私のcsvファイルはなぜか改行がなされていなかったので、行の先頭に顧客名があることを利用して、データをテーブルにフォーマットします。

2. 購入の関連性表を作成する

# create association table
fav_table = []
for i in range(len(matrix[0])):
        fav_row = []
        for j in range(len(matrix[0])):
                fav_row.append([])
                fav_row[j] = 0
                for k in range(len(matrix)):
                        fav_row[j] += matrix[k][i] * matrix[k][j]        
        fav_table.append(fav_row)

先ほどの説明のように、ある商品と他の商品がどのくらいの頻度で組み合わせ購入されているかを調べます。 例えば1行目と3行目ではa1とa3の商品が、10行目と18行目ではa10とa18の商品がどれだけ同時購入されたかわかります。

3. レコメンド用のテーブルをファイルに書き込む

# calculate association rate from table
transposed = [[row[i] for row in fav_table] for i in range(len(matrix[0]))]
def add(x, y): return x + y
sum_table = [reduce(add, transposed[i]) for i in range(len(transposed))]

for i in range(len(fav_table)):
        for j in range(len(fav_table[0])):
                fav_table[i][j] /= float(sum_table[j])

#write association rate table to csv file
try:
        f = open("associated.csv", 'w')
        w = ''
        for i in range(len(fav_table[0])):
                w += "\n"
                for j in range(len(fav_table)):
                        w += repr(fav_table[i][j]) + ', '
        f.write(w)
finally:
        f.close()

例えばa1と組み合わせて購入された履歴が200回あるとします。そのうちa2との組み合わせが150回なら、a1とa2との組み合わせ購入率は150/200 = 0.75となります。

このようにそれぞれの組み合わせ購入率を計算して、レコメンド用のテーブルを更新してファイルに書き込みます。

以上の結果、作成したデータはこんな感じになりました。

ラベルが無いので見難いですが、たとえばA2のデータはa1とa2の商品が組み合わせて購入される確率が0.02とかなり低く、あまりこの組み合わせ購入はおすすめできないことが読み取れます。

もし「あなたにオススメの〜は…」といったサービスを構築する際は、こんな感じのプログラムを作成するのがいいのですが、forループをかなり使うため、大規模なデータを扱う際にはアルゴリズムの改善をするべきだと思います。

追記

a1とa1の商品の組み合わせ購入など、同じ商品同士で購入されることはありえないのですが、今回はその辺の計算を考慮し忘れてしまいました。そのためデータが正確では無いことはご承知下さいませ。


喫煙者の1日を観察する (統計解析版)

By Kousuke Takeuchi on 14 November 2013 | Comments (0)

1週間前くらいに喫煙者がどれだけ損しているかをシュミレーションして解析しましたが、データ処理に関してもっとスマートな方法があったので紹介しようと思います。

前回はRubyでモデルを作ってシュミレーションを行いましたが、今回は「R」を用いて簡単に解析してみます。

そのために、今回用いる分布のモデルを紹介します。 喫煙のように1日あたりの喫煙率がわかっており、これから解析する期間がわかっている場合は「ポアソン分布」を用いると簡単に解析できます。

専門的な数式はこちらを参考にしてください ポアソン分布 - Wikipedia

ポアソン分布は λ, t, kを変数として確率変数をモデリングするのですが、今回の場合はλ = 20本/日, t= 365日 として一番尤もらしいkの値、つまり1年間タバコを吸う場合に一番可能性が高い消費量を計算したいと思います。

さて、「R」でポアソン分布を扱う場合はdpois関数を使います。 ただし、この分布はλとtを掛け算した値をλとして引数にします。なので、今回の場合はλ = 20本/日 × 365日 = 7300本となります。

plot(dpois(7000:7600,7300))

表示される分布データがこちらです。

ポアソン分布の性質からすると当たり前なのですが、分布の平均値はλになるので年間の平均喫煙本数は7300本になります。

ここで分散を考慮すると、 分散σ = λ = 7300本^2

ということで、ポアソン分布が正規分布に近似できることを念頭に、68%の確率に相当する1次分散の範囲を計算すると、 (範囲) = μ ± √σ = 7300 ± √7300 = 7300 ± 85.44…. = [7215, 7385]本

したがって、年間少なくても7215本くらいタバコを吸う確率が高いという分析結果が出ました。 これは、前回のシミュレーションと大分違う値となりました。

なぜでしょうか??

私の見解では、前回のシュミレーションでは喫煙時間を考慮して解析したのですが、今回の統計解析では「一瞬でタバコを吸う」ことを前提に解析しています。したがって、シミュレーション結果よりも解析結果が大幅に外れてしまったんだと思います。

今回のように、データ点が「一点」でない(一瞬ではなく数分で1つのデータ)場合にポアソン分布のようなモデルを安易に適応するといけないことがわかりました。

今回の統計解析は利便性があるかは疑問ですが、シミュレーション解析か統計解析のどちらでデータを解析するか迷った場合は、今回の教訓を参考にしてみてもいいかと思います。


剃ったヒゲで車の模型を作るには、どのくらいのコストがかかるか?

By Kousuke Takeuchi on 09 November 2013 | Comments (0)

今夜は眠れないので、くだらないことをたくさん考えてしまいます。

過去の自分の記念写真を見てると、ファッション感覚でヒゲを生やしている時と、 不潔だから剃ってしまってる時があります。

写真を見てて思いました。

「私から離脱したヒゲたちを集合させて、何かを作ることは出来ないか?」

そこで、試しに「車の模型」を作成する思考実験をしてみたいと思います。

まずは、1ヶ月あたりにゴミとして処理されていくヒゲの全長を計算します。

私は3日に1回ほどヒゲを剃ります。

大体4ミリまで伸びたら剃ります。

したがって、

1ヶ月のヒゲ全長 = 365/12(1ヶ月の平均日数) × 1/3(1日辺りのヒゲを剃る回数) × 4mm(一本あたりのヒゲの長さ) × 20本/cm^2(単位面積:cm^2辺りのヒゲの量) × 7cm^2(ヒゲが散布されている領域) ⇛ 全長 = 5677.8mm/月

私の顎には、縦2cm×横7cmの三角形上にヒゲが分布されていたので、そこからヒゲ領域の面積を導出しました。

さて1ヶ月あたりのヒゲの全長がわかったので、次はヒゲの体積を出したいと思います。

そのためにはヒゲの断面積の情報が必要なのですが、Googleで「ヒゲ 断面図」「Wikipedia ヒゲ」、「Phillips シェーバー 構造」と調べても、全く情報が手に入りませんでした。

しかたがないので、髪の毛とヒゲの断面積がだいたい同じという前提で計算します。

髪の毛の断面積はこちらのサイトを参考にしました。

http://www.shujiro.jp/ke/view9.cgi

1ヶ月で集まるヒゲの全体積 = 5677.8mm/月 × 0.05mm*0.05mm(ヒゲの断面積) = 14.1945mm^3/月

ということで、1ヶ月に集まるヒゲの体積は約14.2mm^3であることがわかりました。

さて、ここから車の模型を作るためにかかる時間を考えてみましょう。

ここで作成する模型の車種は、とりあえず「フォルクスワーゲン ビートル」にします(さっきまでコナンのマジキチSSを見てたので、アガサ博士が乗ってる車を使うことにした)

ビートルの体積は、下のサイトを参考にすると8.5~10.3m^3らしいですね。 とりあえず間をとって9.5m^3辺りで計算してみます。

[フォルクスワーゲンのコーティング,カーフィルム・車種サイズ 専門施工店のIIC](http://www.pro-iic.com/carmodel/car.php?car_no=12)

模型を作成するのにかかる月数 = 9.510^9 mm^3(ビートルの体積) ÷ 14.1945mm^3/月 ≒ 6.6910^8ヶ月 = 5.575*10^7年

したがって、ヒゲでビートルの模型を作成するのにかかる年月は、約55,750,000、つまり5600万年ほどかかるみたいですw キリストでも骨が折れるレベルですね〜

ただし、これは僕一人だけではたいへんなので、日本人の男性の方が全員協力してくれたらどのくらいの期間で完成するのでしょうか?

現在日本の成人は1億人ほど。 そのうち約半分が成人男性なので、約5000万人がヒゲを生やす成人男性です。

おお! これだけの人間が協力してくれるなんて頼もしい!

ただし、全員きちんと協力してくれることは期待していません。頑張っても全体の2割のヒゲしか集まらないでしょう。(パレートによる20:80の法則より)

そこで、全体の2割、つまり1000万人が協力してくれたときの場合を計算してみましょう。

集まるまでの月 = 6.69*10^8ヶ月 / 10^7 = 66.9ヶ月 ≒ 5年と7ヶ月

国全体による総プロジェクトの結果、5年半でヒゲによるフォルクスワーゲン ビートルが完成することがわかりました!

もしヒゲで車の模型を作りたいのならば、8年間の見積もりを立て、3年で成人男性総勢を動員し、5年でひたすらヒゲを集めることをオススメします。


喫煙者の1日を観察する

By Kousuke Takeuchi on 09 November 2013 | Comments (0)

私は毎日1箱タバコを吸う、世間から嫌われし喫煙者なのですが、 年間のタバコのコストなどを計算してみようと思いました。

だいたい1箱440円で1日間吸うのだから、普通に考えて440×365円で計算できるのですが、 これはただのフェルミ推定を行っているだけでツマラナイです。

なので、Rubyを使って1時間ほどかけて喫煙者の一日をシミュレーションするプログラムを作成しましたw 簡単な喫煙者モデルと、喫煙者の一日のモデルを作成してシミュレートしただけなので、細かい説明は省きます。

#喫煙者クラス
class Smoker
        attr :smoking_time #累計喫煙時間
        attr :tobacco #現在所持しているタバコの本数
        attr :tobacco_total #今まで吸ったタバコの本数
        attr :cost #タバコにかけた金額の合計
        attr :interval_average #次のタバコを吸うまでの平均時間(分)
        attr :interval #のタバコを吸うまでの時間(分)

        def initialize(average)
                @smoking_time = 0
                @tobacco = 0
                @tobacco_total = 0
                @cost = 0
                @interval_average = average
        end

        #我慢できないのでタバコを吸う
        def smoke
                #次にタバコを吸うまでの時間(分)
                @interval = 60 * (Random.rand(60) + 1) / @interval_average
                charge_tobacco() unless @tobacco > 0
                @tobacco -= 1
                @smoking_time += 10 #10分間喫煙タイム
                @tobacco_total += 1 #また彼の体内に有害なニコチン・タールが注入されてしまった
        end

        #タバコが無くなったので買いに行く
        def charge_tobacco
                @cost += 440
                @tobacco += 20
        end
end

#喫煙者の1日
class SmokersOneDay
        attr :smoker #喫煙者
        attr :rest_time #喫煙者の1日の残り時間

        def initialize(smoker, rest)
                @smoker = smoker
                @rest_time = rest
        end

        #喫煙者の1日を進める
        def tick
                @rest_time = (@rest_time > @smoker.interval) ? (@rest_time - @smoker.interval) : 0
                @smoker.smoke
                @rest_time = (@rest_time > 10) ? (@rest_time - 10) : 0
        end
end

#時間変換
def to_hour(time)
        return 8 + time/60
end
def to_minute(time)
        return time % 60
end

#喫煙者を作成する
smoker = Smoker.new(45)

#あなたの一日を観察させてほしいとお願いした
#その依頼中に彼は1本タバコを吸っていた
smoker.smoke

#彼から承諾を得たのでレポート用紙を用意する
f = open("smoker_report.txt", "w")

#彼の1年後を楽しみに待つとしよう
365.times do |n|
        #喫煙者の一日が始まる
        one_day = SmokersOneDay.new(smoker, 15*60)

        #早送りで喫煙者の一日をご覧いただこう
        f.write("#{n+1}日目...\n")
        while one_day.rest_time > 0
                one_day.tick
                #観察調査をレポートにまとめる
                now_time = 15*60 - one_day.rest_time #現在の時刻
                hour = to_hour(now_time)
                minute = to_minute(now_time)
                report = "#{hour}:#{minute}, #{smoker.tobacco_total}\n"
                f.write(report)
        end
end

#さて、彼は1年間でいくらのタバコ代を浪費したのだろう?
p "年間のタバコ代"
p smoker.cost
p "年間喫煙時間"
p smoker.smoking_time / 60
p "年間タール摂取量"
p smoker.tobacco * 8

#最後にレポート用紙を上司に提出する
f.close

#これにて喫煙者の調査を終了する

レポートの内容がこちら(一部抜き出し)

21:8, 36
21:42, 37
23:0, 38
3日目...
9:18, 39
10:12, 40
10:50, 41
12:14, 42
13:28, 43
14:43, 44
16:7, 45
16:50, 46
17:14, 47
18:6, 48
19:6, 49
19:26, 50
20:29, 51
21:19, 52
22:31, 53
23:0, 54
4日目...
8:54, 55
9:34, 56

Rubyって話し言葉みたいなんだから、物語チックにコメントを書いてみようと思ったので、 プログラム中のコメントをふざけさせて頂きましたw

実行結果した結果、1年間のタバコ代は148720円とのことでした(ランダムシミュレーションなので、実行ごとに多少の誤差は発生します) これは最初に計算した、440×365=160600円とかなり近い計算になります。

ただしフェルミ推定の方は、かなりアバウトな計算になるのでシミュレーション結果の方の金額が現実的であると思います。

今回のシミュレーションでわかったことは、普通にタバコを吸ってるだけで 年間iMacを1台買い換えれるくらいのお金をドブに捨ててるわけですね。

ただ一番びっくりしたのが、1年間の内タバコを吸ってる時間が1000時間を超えること。 つまり、1年間のうち1ヶ月以上を喫煙時間で消費してしまってるのですね。 タバコを吸ってると他のオフィスの人とコミュニケーションが取れるから、営業で案件を取ってくる際にやりやすいと思ってたのですが、えらくコストの掛かるコミュニケーションだなって思いましたw

以上。


「あいまいな哲学」か「決定的な哲学」か?

By Kousuke Takeuchi on 09 November 2013 | Comments (0)

昨日は寝不足でひどく疲れてたのか、今日起きて自分のメモノートを確認してみると哲学に関する考察が殴り書きされていたので、自分の思考についてまとめてみようと思います。

「曖昧な哲学」(= P) か、「決定的な哲学」(= Q) か?

(§) PかQのどちらが正しいかについて考察するにあたって、正当性を「人類の発展に寄与するかどうか」の観点で行うとする

(i) 人類の発展とは如何なるものか。そこで人類の発展史を抽象的に捉えてみるとしよう。よく学問の進歩のためには過去の叡智を批判的に捉えることが重要と一般的に言及される。そこで人類の発展を「過去の定理を変更する」こと、つまり創造的破壊こそ人類の発展であると狭義する。なぜそのように定義するのかというと、ある恒久的な定理が存在するとすると、その定理が扱う分野を完全に停滞させてしまうからである。

R したがって、ある時点tにおいてQ(t)が成り立ち、それをQ(t’)で変更するとする。(Q(t)の否定でなくとも) ⇛ 「人類の発展に寄与した」

このときRについて考える。Rの推論が真であるならば、Q(t) = Rのときはどのようになるのだろうか? Qを変更することが「人類の発展」となるならば、Q=Rのとき、Rを変更することも「人類の発展」となってしまう。したがって、Rからは自己矛盾が発生するため、R推論の真偽は決定的でない。

(ii) Rではなく次のSについて考えてみる。

S ある時点でP(t)が成り立ち、それをP(t’)で変更する ⇛ 「人類の発展に寄与した」

このようなSを考慮する事自体おかしなことは自明である。なぜならば、Pというのは「非決定的」なため、P自体の真偽がtにおいて特定できない。したがって、Sも非決定的である。

(i), (ii)より、(§)や「定理の創造的破壊が人類の発展に寄与する」という視点からはP, Qの正当性を決定出来ない。したがって、私は哲学を今回言及した「前提」で行っている者に述べたい。

あなたがたが鼻高々と学問している哲学というものは、本当に人類の進展に寄与しているのか? もう少しそのことについても考えても良いのではないのか。あなたが哲学者であるのならば。


Webサーバーのログ情報を視覚化する

By Kousuke Takeuchi on 09 November 2013 | Comments (0)

サーバーエンジニアをやってると、シェルスクリプトでログ情報を編集してホームページなどのアクセスランクを作ることは多いと思いますが、経営者やデザイナーなどエンジニア以外の人にとっては、その(ほとんど生な)データは見ても情報を見抜きにくい上、面白くないのでアイデアがわきにくいと思います。

そこで、このつまらないサーバーログをProcessingを使ってわかりやすくネットワークグラフとして表示するプログラムを紹介します。

グラフの描画に関しては「ビジュアライジング・データ」を参考に作成しました。

まずは、サーバーのログをProcessingで扱いやすいように整形します。

#!/bin/bash
log=/var/log/nginx/access.log  #path to log file

# except the acccess history of me
# then, shape it, and save as a text
cat $log | grep -v 192.168.10.3 | cut -d ' ' -f 6 | sort | uniq -c | sort -r | grep .html | sed -r 's/.html//g' > access_rank.txt

実行結果は以下のようになります。

… 2 company message 2 company index 2 company companyinfo 1 update character 1 sitemap 1 site introduction index 1 service 1 ricruit …

今回は最近作成したNginxのサーバーのログファイルを整形したので、情報量が少なかったのです。

さて、この空白で区切られたデータを表示するプログラムを紹介していきます。

ネットワークを構成するためには「ノード」と「エッジ」が必要になります。今回は「ノード」がHTMLページ、「エッジ」が「ノード」つなぐための「線」になります。

まずは、この2つをクラスによって定義していきます。

class Node {
  float x, y;
  float dx, dy;
  boolean fixed;
  String label;
  int count;

  Node(String label) {
    this.label = label;
    x = random(width);
    y = random(height);
  }

  void relax() {
    float ddx = 0;
    float ddy = 0;

    for (int j = 0; j < nodeCount; j++) {
      Node n = nodes[j];
      if (n != this) {
        float vx = x - n.x;
        float vy = y - n.y;
        float lensq = vx * vx + vy * vy;
        if (lensq == 0) {
          ddx += random(1);
          ddy += random(1);
        } else if (lensq < 100*100) {
          ddx += vx / lensq;
          ddy += vy / lensq;
        }
      }
    }
    float dlen = mag(ddx, ddy) / 2;
    if (dlen > 0) {
      dx += ddx / dlen;
      dy += ddy / dlen;
    }
  }

  void update() {
    if (!fixed) {
      x += constrain(dx, -5, 5);
      y += constrain(dy, -5, 5);

      x = constrain(x, 0, width);
      y = constrain(y, 0, height);
    }
    dx /= 2;
    dy /= 2;
  }

  void draw() {
    if (fixed) {
      fill(nodeColor);
      stroke(0);
      strokeWeight(0.5);

      String content = label + " " + count;
      float w = textWidth(content) + 10;
      float h = textAscent() + textDescent() + 4;
      ellipse(x, y, w*pow(1.06, count-1), h*pow(1.06, count+1));

      fill(0);
      textAlign(CENTER, CENTER);
      text(content, x, y);
    } else {
      fill(nodeColor);
      stroke(0);
      strokeWeight(0.5);
      ellipse(x, y, sqrt(count)*10, sqrt(count)*10);
    }
  }

  void increment() {
    count++;
  }
}

Node findNode(String label) {
  label = label.toLowerCase();
  Node n = (Node) nodeTable.get(label);
  if (n == null) {
    return addNode(label);
  }
  return n;
}

Node addNode(String label) {
  Node n = new Node(label);
  if (nodeCount == nodes.length) {
    nodes = (Node[]) expand(nodes);
  }
  nodeTable.put(label, n);
  nodes[nodeCount++] = n;
  return n;
}

class Edge {
  Node from;
  Node to;
  float len;
  int count;

  Edge(Node from, Node to) {
    this.from = from;
    this.to = to;
    this.len = 50;
  }

  void relax() {
    float vx = to.x - from.x;
    float vy = to.y - from.y;
    float d = mag(vx, vy);
    if (d > 0) {
      float f = (len -d) / (d * 3);
      float dx = f * vx;
      float dy = f * vy;
      to.dx += dx;
      to.dy += dy;
      from.dx -= dx;
      from.dy -= dy;
    }
  }

  void draw() {
    stroke(edgeColor);
    strokeWeight(0.35);
    line(from.x, from.y, to.x, to.y);
  }

  void increment() {
      count++;
    }
}

void addEdge(String fromLabel, String toLabel) {
  Node from = findNode(fromLabel);
  Node to = findNode(toLabel);
  from.increment();
  to.increment();

  // check whether the Edge have already existed.
  for (int i = 0; i < edgeCount; i++) {
     if (edges[i].from == from && edges[i].to == to) {
         edges[i].increment();
         return;
     }
  }

  Edge e = new Edge(from, to);
  e.increment();
  if (edgeCount == edges.length) {
    edges = (Edge[]) expand(edges);
  }
  edges[edgeCount++] = e;
}

クラス内のメゾッドにrelax(), update(), draw()があり、これらがグラフを上手く表示するようにエッジやノードの位置を調節するそうです。詳しいことは他の文献を参考にしろとのことでした。 また、ビジュアライジング・データには載っていませんでしたが、アクセス数の多いページのノードを大きく表示するように自身で改良しました。

参考: ゲーム開発のための物理シミュレーション入門―Physics for Game Developers

さて、続いてエッジに先ほどのデータを追加していきます。

void loadData() {
  reader = createReader("/path/to/access_rank.txt"); // 先ほどシェルスクリプトで整形したデータへのパス
  try {
    String line = reader.readLine();
    while (line != null) {
      String[] columns = split(line, ' ');
      String fromEdge = INDEX;
      int num = 0;
      for (String word : columns) {
        int count = 0;
        if (!word.isEmpty() && isNumeric(word)) {
          num = Integer.parseInt(word);
        } else if (!word.isEmpty() && !word.toLowerCase().contains(INDEX) && num != 0) {
          for (int i = 0; i < num; i++)
            addEdge(fromEdge, word);
          fromEdge = word;
        }
      }
    line = reader.readLine();
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
}

public static boolean isNumeric(String str)  
{  
  try  
  {  
    Integer d = Integer.parseInt(str);  
  }  
  catch(NumberFormatException nfe)  
  {  
    return false;  
  }  
  return true;  
}

これはProcessingのsetup()関数で呼び出されます。一行ごとにページヘのパスを調べてネットワークを構成します。

プログラムの大まかな構成は上記のような感じです。あとはマウスがクリックされた時の振る舞いや、ノードの色などを決めたりするだけなので、その辺りは読み飛ばしていってもらったらいいかと思います。

最後にプログラムの全体を掲載しておきます。

BufferedReader reader;

int nodeCount;
Node[] nodes = new Node[100];
HashMap nodeTable = new HashMap();

Node selection;

int edgeCount;
Edge[] edges = new Edge[500];

static final color nodeColor = #F0C070;
static final color selectColor = #FF3030;
static final color fixedColor = #FF8080;
static final color edgeColor = #000000;

PFont font;
static final String INDEX = "index";

void setup() {
  size(1000, 600);
  loadData();
  font = createFont("SansSerif", 10);
  textFont(font);
  smooth();
}

void loadData() {
  reader = createReader("/path/to/access_rank.txt");
  try {
    String line = reader.readLine();
    while (line != null) {
      String[] columns = split(line, ' ');
      String fromEdge = INDEX;
      int num = 0;
      for (String word : columns) {
        int count = 0;
        if (!word.isEmpty() && isNumeric(word)) {
          num = Integer.parseInt(word);
        } else if (!word.isEmpty() && !word.toLowerCase().contains(INDEX) && num != 0) {
          for (int i = 0; i < num; i++)
            addEdge(fromEdge, word);
          fromEdge = word;
        }
      }
    line = reader.readLine();
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
}

public static boolean isNumeric(String str)  
{  
  try  
  {  
    Integer d = Integer.parseInt(str);  
  }  
  catch(NumberFormatException nfe)  
  {  
    return false;  
  }  
  return true;  
}

void draw() {
  background(255);

  for (int i = 0; i < edgeCount; i++)
    edges[i].relax();
  for (int i = 0; i < nodeCount; i++)
    nodes[i].relax();
  for (int i = 0; i < nodeCount; i++)
    nodes[i].update();
  for (int i = 0; i < edgeCount; i++)
    edges[i].draw();
  for (int i = 0; i < nodeCount; i++)
    nodes[i].draw();
}

void mousePressed() {
  float closest = 20;
  for (int i = 0; i < nodeCount; i++) {
    Node n = nodes[i];
    float d = dist(mouseX, mouseY, n.x, n.y);
    if (d < closest) {
      selection = n;
      closest = d;
    }
  }
  if (selection != null) {
    if (mouseButton == LEFT) {
      selection.fixed = true;
    } else if (mouseButton == RIGHT) {
      selection.fixed = false;
    }
  }
}

void mouseDragged() {
  if (selection != null) {
    selection.x = mouseX;
    selection.y = mouseY;
  }
}

void mouseRelesed() {
  selection = null;
}

class Node {
  float x, y;
  float dx, dy;
  boolean fixed;
  String label;
  int count;

  Node(String label) {
    this.label = label;
    x = random(width);
    y = random(height);
  }

  void relax() {
    float ddx = 0;
    float ddy = 0;

    for (int j = 0; j < nodeCount; j++) {
      Node n = nodes[j];
      if (n != this) {
        float vx = x - n.x;
        float vy = y - n.y;
        float lensq = vx * vx + vy * vy;
        if (lensq == 0) {
          ddx += random(1);
          ddy += random(1);
        } else if (lensq < 100*100) {
          ddx += vx / lensq;
          ddy += vy / lensq;
        }
      }
    }
    float dlen = mag(ddx, ddy) / 2;
    if (dlen > 0) {
      dx += ddx / dlen;
      dy += ddy / dlen;
    }
  }

  void update() {
    if (!fixed) {
      x += constrain(dx, -5, 5);
      y += constrain(dy, -5, 5);

      x = constrain(x, 0, width);
      y = constrain(y, 0, height);
    }
    dx /= 2;
    dy /= 2;
  }

  void draw() {
    if (fixed) {
      fill(nodeColor);
      stroke(0);
      strokeWeight(0.5);

      String content = label + " " + count;
      float w = textWidth(content) + 10;
      float h = textAscent() + textDescent() + 4;
      ellipse(x, y, w*pow(1.06, count-1), h*pow(1.06, count+1));

      fill(0);
      textAlign(CENTER, CENTER);
      text(content, x, y);
    } else {
      fill(nodeColor);
      stroke(0);
      strokeWeight(0.5);
      ellipse(x, y, sqrt(count)*10, sqrt(count)*10);
    }
  }

  void increment() {
    count++;
  }
}

Node findNode(String label) {
  label = label.toLowerCase();
  Node n = (Node) nodeTable.get(label);
  if (n == null) {
    return addNode(label);
  }
  return n;
}

Node addNode(String label) {
  Node n = new Node(label);
  if (nodeCount == nodes.length) {
    nodes = (Node[]) expand(nodes);
  }
  nodeTable.put(label, n);
  nodes[nodeCount++] = n;
  return n;
}

class Edge {
  Node from;
  Node to;
  float len;
  int count;

  Edge(Node from, Node to) {
    this.from = from;
    this.to = to;
    this.len = 50;
  }

  void relax() {
    float vx = to.x - from.x;
    float vy = to.y - from.y;
    float d = mag(vx, vy);
    if (d > 0) {
      float f = (len -d) / (d * 3);
      float dx = f * vx;
      float dy = f * vy;
      to.dx += dx;
      to.dy += dy;
      from.dx -= dx;
      from.dy -= dy;
    }
  }

  void draw() {
    stroke(edgeColor);
    strokeWeight(0.35);
    line(from.x, from.y, to.x, to.y);
  }

  void increment() {
      count++;
    }
}

void addEdge(String fromLabel, String toLabel) {
  Node from = findNode(fromLabel);
  Node to = findNode(toLabel);
  from.increment();
  to.increment();

  // check whether the Edge have already existed.
  for (int i = 0; i < edgeCount; i++) {
     if (edges[i].from == from && edges[i].to == to) {
         edges[i].increment();
         return;
     }
  }

  Edge e = new Edge(from, to);
  e.increment();
  if (edgeCount == edges.length) {
    edges = (Edge[]) expand(edges);
  }
  edges[edgeCount++] = e;

そして、実行した結果がこちらです。

やっぱりデータが少ないので、少しさびしい印象を与えますね^^; 今回はホームページのアクセスログを解析しました。なのでノードの数は少なくて観やすかったと思います。(ページの数が50を超えるような大規模なホームページの場合はかなり見難いと思いますが) このように、ノード数が少ないデータを可視化したいのなら、Processingでネットワークグラフを作成してみるといいかと思います。


「文武両道」は現実的か? (3)

By Kousuke Takeuchi on 24 October 2013 | Comments (0)

さて、前回まででデータをnumpyにフォーマットする作業が終わったので、 とりあえず相関の検定をしてみます。

統計解析ツール「R」では、無相関検定というものがあります。 要点だけ説明すると、無相関検定によって出力される「p値」が0.05よりも小さい場合は、2つのデータ間に「相関が無いことは無い」ことが証明されます。 なぜこんな遠回しの言い方をするのかというと、この検定によって無相関性が否定されても、「だからといって完全に相関があるかどうかはわからない。相関が無い可能性は低いことしかわからない。」といったことしか分からないからです。

今回は細かい議論は一旦置いといて、とりあえずRで無相関検定を行います。 ただし、Numpyにフォーマットしたデータをcsvに保存しても、そのデータはNumpy形式の数字で保存されるので、直接Rでは使うことができません。

したがって、Python上で動くRのモジュールである「rpy2」を使うことにしました。 rpy2によってNumpyのデータをRで解析したり、Pythonプログラムの中でRを使えたりします。

ちなみに、NumpyやMatplotlib、rpy2などのモジュールは、「easy_install」を使って簡単にインストールできます! 詳しくは以下のドキュメンテーションを読んでください。 setuptools 1.1.6 : Python Package Index

さて、早速今回作成したプログラムを紹介します。(最後の数行がrpy2の呼び出しで、残りは前回同様)

import xlrd
import numpy as np
#import matplotlib.pyplot as plt
import rpy2.robjects as r
import rpy2.robjects.numpy2ri as n2r

## read lerning assessment excel file and plot histgram
book = xlrd.open_workbook("unformatted_datas/learning_assessment.xls")
sh = book.sheet_by_index(0)

results = np.zeros((4,47))
row = 0
for i in (14, 18, 22, 26):
        col = 0
        for j in xrange(14, 61):
                results[row][col] = sh.cell_value(rowx=j, colx=i)
                col += 1
        row += 1

average_each_prefecture = results.mean(0)


## read physical survey text file and plot histgram
f = open('unformatted_datas/physical_survey.txt')
lines = f.readlines()
f.close

physical_results = np.zeros((4,47))

i = 0
col = 0
for line in lines:
        if i > 1:
                line2 = line.split()
                fixed_line = line2[2:4] + line2[5:7]
                for row in range(0, 4):
                        physical_results[row][col] = fixed_line[row]
                col += 1
        i += 1

average_each_prefecture2 = (physical_results[0] + physical_results[2]) / 2

## correlations test
n2r.activate()
cortest = r.r['cor.test']
print cortest(average_each_prefecture, average_each_prefecture2)

Rには無相関検定を行う「cor.test()」という関数があるので、これをrpy2によって使用します。 ただしNumpyをRで使うために、「numpy2ri」をアクティベートするのを忘れないでください。

そして、実行した結果がこちら

Pearson’s product-moment correlation

data: structure(c(14.75, 14.675, …14.975, 12.9), .Dim = 47L) and 44.37, 47.685, 44.19, 44.4), .Dim = 47L) t = 3.0029, df = 45, p-value = 0.004355 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.1375490 0.6226846 sample estimates: cor 0.4085772

注目していただきたいのは、真ん中辺りの「p-value」です。これが最初に言及した「p値」のことです。

この検定でp値は0.004355となり、0.05よりも小さいので、この検定は「帰無仮説は棄却され、結果は統計的に有意である」ことが分かりました。

つまり何が言えるかというと、勉強ができる人は運動もできる可能性が高いとこがわかったということです。

今回の実験は、検定の下準備(Shapiro-Wilk検定や分散の統一性の検定)などが省かれており、あまり信用のいかない検定のまま終えることにしました。実際におっぴらに公表できる検定ではありませんが、Pythonを使ってここまで世の中のことを統計解析できることを示したかったので、あまり難しすぎる内容は省こうと思ったからです。

もしPythonやRが使える方は、ぜひこのような流れで社会を調べてみましょう!

最後に、現在「Rによる計量経済学」を読んでいます。数学をもっと世の中のことに適応させるためには、経済学に触れてみるのもいいかと思って勉強することにしました。


「文武両道」は現実的か? (2)

By Kousuke Takeuchi on 23 October 2013 | Comments (0)

昨日に続き、文部科学省のホームページから体力テストに関するデータを取得したので、前回の学力テストのデータと合わせて解析していきます。

といっても、体力テストの調査データはExcelで配布されておらず、PDFに記載されているデータをテキスト形式に自分で変換してから使用することにしました。 今回の解析の準備段階で、日本の統計データの集計に関するフォーマットがあまりにもまとまってないことを知ることが出来ました。 プログラミングや英語を授業で必修化して、世界の傾向に歩調を合わせようとするのなら、今ビジネス界でホットトピックである統計解析をもっとやりやすいように集計データのフォーマットについても考えてほしいものですね(せめてExceldで配布しろよw)

とりあえず、文科省のページリンクとテキスト形式にフォーマットしたファイルを紹介しときます。 平成24年度全国体力・運動能力、運動習慣等調査結果:文部科学省(中学生実技調査のデータを参考にしました) physical_survey (テキストデータにフォーマットしたもの)

さて、このphysical_servey.txtと前回の学力データを元に、簡単な散布図を表示してみます。

import xlrd
import numpy as np
import matplotlib.pyplot as plt

## read lerning assessment excel file and plot histgram
book = xlrd.open_workbook("learning_assessment.xls")
sh = book.sheet_by_index(0)

results = np.zeros((4,47))
row = 0
for i in (14, 18, 22, 26):
col = 0
for j in xrange(14, 61):
results[row][col] = sh.cell_value(rowx=j, colx=i)
col += 1
row += 1

average_each_prefecture = results.mean(0)


## read physical survey text file and plot histgram
f = open('physical_survey.txt')
lines = f.readlines()
f.close

physical_results = np.zeros((4,47))

i = 0
col = 0
for line in lines:
if i > 1:
line2 = line.split()
fixed_line = line2[2:4] + line2[5:7]
for row in range(0, 4):
physical_results[row][col] = fixed_line[row]
col += 1
i += 1

average_each_prefecture2 = (physical_results[0] + physical_results[2]) / 2

plt.scatter(average_each_prefecture, average_each_prefecture2)
plt.show()

21行目からが、今回追加したプログラムです。 前回同様、データをNumpyの行列形式に変換し、それぞれの県に関して平均値をとるという簡単なプログラムです。 そして、最後にMatplotlibのPyplotでScatter(散布図)を表示させるって感じの流れになります。

実際にプロットした散布図がこちら

なんとなく右肩上がりなグラフになっている気がします。 もし、右肩上がりの相関があるのなら、勉強ができる子供は運動もできるという仮説が確実になってきます。 ただし、あくまでも「右肩上がりに見える」だけなので、きちんと数学的に検定をしなければ仮説が証明されたことにはなりません。(きれいな一直線上に点が並んでるとも言い難いので)

次回は、検定などの統計解析ツールである「R」を導入して、このグラフの相関性を確認するプログラムを作成していきたいと思います。


「文武両道」は現実的か?

By Kousuke Takeuchi on 22 October 2013 | Comments (0)

去年塾でアルバイトをしている時に、生徒から 「勉強できる人は運動もできるの??」 ってことをよく質問されました。

私は勉強も運動も努力がほとんどだと思うので、勉強で努力できる人は運動でも努力でき、学力と体力は相関関係が必ずあると思います。 ただし、この仮説はあくまでも経験的なものであり、経験バイアスによって過信してしまっているだけなのかもしれません。 具体的に調査された結果を知っているわけでもないので、私はこの仮説に関して検定するプログラムを作ってみることにしました。

とりあえず大学のゼミでNumpyとMatplotlibについて勉強しているので、Pythonを使って データを解析していきます。

学力と体力のデータに関しては、国立教育政策研究所のホームページに25年度の学力調査の結果がExcel形式で配布されていたので、これを使うことにしました。 平成25年度 全国学力・学習状況調査 報告書 調査結果資料:国立教育政策研究所 National Institute for Educational Policy Research

今回はとりあえず、学力データに関する解析だけ簡単にやってみます。

ホームページの「実施概要」欄にある、「中学生」のリンクをクリックしてExcelファイルをダウンロードして下さい。

実際にExcelのデータを見てみると、右端の列に県ごとのテストの平均があるので、 これらから各都道府県の学力数値をpythonの配列に入れてHistgramに表示するところまでプログラムを作りました。

実際に作成したプログラムはこちら ちなみに、Excelのデータはlearning_assessment.xlsで保存しています。

import xlrd
import numpy as np
import matplotlib.pyplot as plt

book = xlrd.open_workbook("learning_assessment.xls")
sh = book.sheet_by_index(0)

results = np.zeros((4,47))
row = 0
for i in (14, 18, 22, 26):
col = 0
for j in xrange(14, 61):
results[row][col] = sh.cell_value(rowx=j, colx=i)
col = col + 1
row = row + 1

average_each_prefecture = results.mean(0)
plt.hist(average_each_prefecture, 40)
plt.show()

そして、ヒストグラムが以下のようになりました。

横が学力平均、上が範囲に属する都道府県の数です。 うーん、正規分布に沿っているのかどうかがイマイチ分からない。。。

ということで、これからやるべきことは以下の3つ

  1. 体力に関するデータをpythonで処理できるように準備する
  2. データが正規分布に沿っているかをShapiro-Wilk検定で確認
  3. 学力データと体力データの相関性をt検定によって調査する

以上


計画力に関して(2)

By Kousuke Takeuchi on 10 October 2013 | Comments (0)

計画の計画をすることにどのような意義があるのでしょうか?

あなたやあなたの周りの人達は、目標を達成し夢を叶えるために何かしら目標を立てて実行していると思います。(全く計画も立てずに行動する人もいますが) ただし、多くの目標というのは最初はやる気があって実行できるのですが、途中でやる気を落とし、もともと計画のことで頭がいっぱいだったはずなのにいつの間にか他のことに時間を追われて、結局計画通りに行かないものだと思います。これは人間であれば当然で、動物は一箇所にとどまっていると敵から見つかる確率が高くなるため、本能的に変化を求めるように人間の脳はできているのだと僕は考えています。

つまり、長期的な計画を立て、すべて計画通りに実行できる人間は普通ではありえないと思います。(もし完璧に計画を実行できる人間がいれば、その人間が天才であるか、もしくはとても低い目標しか立てられない人間であると思います)

そこで長期的な計画を立てる際には、途中で計画通りに実行できずに挫折することを想定して、計画変更を行う期限を決定すべきだと思います。もちろん期限まで計画通りに実行できていればとてもいいと思います。しかし、計画を立てた当日というのはとてもやる気に満ちていて、その日だけは大変な計画でも実行することができてしまうと思います。

したがって、計画の計画を立てる場合はできるだけ計画変更の期限を短いスパンで用意しておくことが望ましいと思います。計画を立てても3日以内に挫折してしまう人は、少なくとも計画を立ててから3日間はやる気が持続するということなので、3日立ったらすぐに計画を立て直し、また3日立ったら計画を立て直すといったやり方をしていけば良いと思います。

僕の場合は、高校2年の終わりの頃に計画を立てて勉強するようになりましたが、当然それまでまじめに継続して勉強することがなかったので、計画を立てても基本的に2日もやる気は持続しませんでした。しかし、2日ごとに計画を立てなおしてはやる気を出して、また挫折して計画を立て直すことを繰り返していくうちに、だんだん自分にあった無理の無い計画を立てるコツが分かるようになりました。さらに、自分にあった計画によって、だんだん2日で挫折することが少なくなり、最終的には1ヶ月間の計画を完璧に実行できるようになりました。

今回計画の計画について最初に文章を書こうと思ったのも、計画力の基本的な体得方法が、スケジュールを立てて何度も計画を立てる練習をすることだからと思ったのが所以です。

最後に今回の文章をまとめると、重要な点は以下の3つ ・挫折することを想定して計画を立てる ・計画を立てた時のモチベーションをうまく利用するために計画を変更することを恐れない(モチベーション・マネジメントの一種) ・むしろ、計画を変更することは自身の計画力の向上につながる。

次回は計画法の本質的なところを書いていこうと思います。


計画力に関して(1)

By Kousuke Takeuchi on 08 October 2013 | Comments (0)

大学受験を乗り越え、大学に入ってから3社のベンチャーで仕事を経験した結果、計画性の大切さについて改めて思い知らされました。

特に大学受験時代に経営学の本を参考に、偏差値を45から70近くまで1年間で上げるにはどうすればよいかひたすら実践しながら学んでいました。

結果目標は達成されて、私は自分の計画術に自信を持っていたのですが、大学2回生の時にやってた塾のバイトでこの計画術を高校受験生に教えようとしましたが、ほとんど伝えられないまま私は塾をやめることになりました…

そこで、もっと計画術を一般の方にも広めたいと思っているのですが、具体的に私の計画術のどこが現実的に実行しづらいかを知りたいので、ぜひいろんな人からのご意見をいただきたいと思っております。

さて、具体的に計画術を伝えていく前に、おおまかに計画術の構成をまとめてみます。 私の周りの多くの人も同様、多くの人が計画を立てても実行できない、ムリな計画を立ててしまうという悩みをお持ちだと思います。こうやって偉そうに博識ぶったクズの私でも、未だにきちんと計画を立てて実行することができません。そこで発想を逆転させて、計画を立てても途中で挫折することを想定した計画の立て方を学ぶべきだと思いました。

また、計画を立てるといっているので、やりたいことをリストアップして実行順序や実行日時を決定する必要があります。その順序の構成方法についてもおおまかに話していこうと思います。

最後に、計画を立てる前にToDoリストを作成しますが、そのToDoリストの作成についても考慮すべき事項がありますので、そこ点についても話していきます。

なので、これから話す計画術については以下の3つ(本当はもっとたくさんの計画方法があるのですが、多くの人に共通することを話したかったので、とりあえず3つ)

  1. 挫折を想定した計画法(私は計画の計画法と呼んでいます)
  2. 計画の順序(これが計画法の本編)
  3. 計画する前にするべきこと(プレ計画法)

一回の記事に内容を詰め込みすぎると私も読者側も疲れてしまうので、今回は今後の記事の紹介ということで締めさせていただこうと思います。


Generated by Jekyll and served out of Github Pages