こんにちは。
今回は、2014年のオリコンのランキングとボカロのランキングを比較して、いろいろ楽しみたいと思います。
いま流行りの統計的な手法やテキストマイニングを使います♪
スクレイピング
では、さっそく、データを集めましょう。
オリコンはこちらのデータを見ました。
累計ポイントランキング - ぼからんまとめ
ボカロのランキングはこちらを見ました。
まずは、ここから曲のリストを取り出します。取り出すことをスクレイピングと言うそうです。
スクレイピングのスクリプトを書きましたよ!
require 'cgi' page_source = open("ランキングのページのパスをいれるよ〜", &:read) # scanは、マッチした個数に関係なく配列に入れてくれるので、配列から外す(flattenとこ) ranks = page_source.scan(/<span class=\"rank\">(.+?)<\/span>/).flatten! titles = page_source.scan(/<span class=\"title\"><a.+?>(.+?)<\/a><\/span>/).flatten! # ランクとタイトルの配列をまとめてひとつにする list = ranks.zip(titles) list.each{|a| print CGI.unescapeHTML(a.join(",")) + "\n" }
こういう感じになりました!
使った言語はRubyです。上記はボカロのバージョン。オリコンのほうはもっとカンタンだったので、正規表現を使って検索&置換で取り出しました。
こうして、ランキングの曲をカンマ区切りのデータにします。
2,『初音ミク』千本桜『オリジナル曲PV』
3,【初音ミク】 ウミユリ海底譚 【オリジナル曲】
4,GUMI MV「ドーナツホール」
5,【初音ミク】 恋愛裁判 【オリジナルMV】
タイトル部分には、タイトル以外の要素(「【初音ミク】」とか「【オリジナル曲】」とか)もあります。これを取り除きましょう。つまり、
2,千本桜
3,ウミユリ海底譚
4,ドーナツホール
5,恋愛裁判
100曲分のリストができましたので、今度はそれぞれの歌詞を手にしましょう。歌詞は初音ミク Wikiに載っているので、ここからスクレイピングすることを目論みます。
さっき作ったリストをRubyで読み込んで、歌詞を1曲ずつテキストファイルに書き出します。
# 曲目のリストCSVファイルを受け取って、各曲の歌詞をテキストで書き出す require 'open-uri' require 'csv' require 'uri' require 'cgi' CSV.foreach("さっき作ったCSVのパス", "r") do |row| # 1列目にはタイトルが入ってる title = row[1] # このURLにアクセスするとなんか直接それぞれの曲のページに行ける url = "http://www5.atwiki.jp/hmiku/?page="+title url = URI.escape(url) # htmlを読む html = open(url) do |f| f.read end if (/<title>初音ミク Wiki -.*は見つかりません<\/title>/ =~ html) next end begin # 歌詞を拾う lyric = html.scan(/<h3 id=\"id_0a172479\">歌詞<\/h3>.*?<div>(.+)<\/div>.*?<h3/m)[0][0] rescue # なにもしない end if (lyric == nil) next end # 歌詞をきれいに lyric = CGI.unescapeHTML(lyric) lyric.gsub!(/<div>|\n/m, '') #divタグと\nを取り除く lyric.gsub!(/<\/div>|\n/m, '<br />') #div閉じタグを<br \/>にする lyric.gsub!(/\A\s+?|\s+?\Z|(<br \/>)+\Z/m, '') #文字列の最初と最後の空白や<br />を取り除く lyric.gsub!("<br />", "\n") #brタグを改行にする lyric.gsub!(/<\/?[^>]*>/, "") #文中のhtmlタグは外す lyric.chomp! File.write("歌詞のテキストファイルをしまうフォルダのパス"+row[0]+"_"+title+".txt", lyric) sleep 1 end
こうすると、指定したフォルダに山ほどテキストファイルができます!
ほらね!!
テキストファイルには歌詞のルビが付いてたり、出典が付いてたりします。それはいらないので、手作業で消しました。これでスクレイピング完了!!
まとめ
- Rubyを使って、ランキングから曲目のリストを取り出し、リストから個々の歌詞を取り出した。
今後の課題
- スクレイピングはただの通過点なので、ここに1週間とかかけるのやめたい。
- ルビとか手作業で取るのかなりダルかったので、今後はここも自動でなんとかなるようにしたい。
数を数える!
準備ができました。
では、いよいよ分析っぽいことしてみます。
手始めに、オリコン、ボカロのそれぞれの曲について、行数などを数えてみます。
# フォルダを指定すると、その中のファイルを読み込んで、字数、行数、連数をカウントします mydirs = ["オリコンの曲が入ったフォルダのパス", "ボカロの曲が入ったフォルダのパス"] mydirs.each{|mydir| # line, paragraph, char, ファイル名 c = [] l = [] p = [] Dir.open(mydir){|dir| # フォルダの中のファイル1つずつについて操作 dir.each{|filename| # 「.」から始まる名前のファイルは無視 next if /^\./ =~ filename # ファイル関係のエラーが起きたときのため begin lyric = File.open(mydir + "/" + filename, &:read) c.push(lyric.split(//).length) l.push(lyric.split(/\n/).length) p.push(lyric.split(/\n\n+?/).length) rescue => e print "error\t" + e.message + "\tname:" + filename + "\n" end } } print mydir + "\n" print "c" + c.to_s + "\n" print "l" + l.to_s + "\n" print "p" + p.to_s + "\n" }
すると、こんな感じで数字がわーっと出てきます。ひょー!!
pが連、lが行、cが文字の数を示しています。連というのは、空白の行で区切られたかたまりです。
1行目には「l[65, 65, 49, 32,…」って書いてありますが、これは読み込まれたオリコン曲が、1曲目から順に65行、65行、49行、32行あることを示しています。順番はRubyのオリジナルルールになるらしく、1位の次は10位になったりしてるみたいです。
この、65, 65, 49, 32,という並びから察するに、オリコン曲はだいたい50行ぐらいあるってことみたい。ボカロ曲とどれぐらい違うか比べたいですね。
ということで、今度は別のプログラムを書いてみます。
比較のためには統計を活用したいです。統計に便利と言われている、Rという言語に乗り換えましょう。
RはRubyと名前が似ていますが、別の言語です。
oricon_c <- c(742, 720, 491, 567, 522, 697, 481, 644, 523, 538, 579, 582, 863, 658, 743, 504, 536, 628, 474, 639, 631, 936, 803, 852, 812, 722, 732, 1012, 1279, 441, 693, 813, 988, 624, 499, 767, 603, 674, 590, 526, 1079, 735, 1182, 637, 701, 565, 631, 409, 1028, 970, 1164, 676, 442, 242, 651, 508, 477, 1046, 1195, 228, 632, 477, 440, 1145, 193, 594, 583, 980, 1200, 620, 633, 1107, 298, 960, 849, 1446, 592, 610, 457, 467, 1230, 551, 748, 654, 378, 847, 698, 424, 600, 693, 262, 1075, 545, 582, 587, 326, 804, 858, 712, 853) oricon_l <- c(65, 65, 49, 32, 72, 87, 66, 60, 41, 58, 74, 66, 60, 61, 60, 44, 38, 42, 25, 40, 43, 46, 79, 53, 47, 41, 46, 73, 64, 45, 75, 49, 89, 70, 61, 46, 69, 49, 45, 35, 58, 67, 63, 47, 77, 35, 59, 38, 76, 73, 96, 34, 23, 20, 32, 72, 38, 78, 87, 20, 48, 36, 63, 70, 17, 52, 35, 58, 81, 39, 41, 45, 23, 87, 46, 64, 44, 35, 36, 59, 77, 28, 60, 61, 41, 78, 41, 43, 65, 58, 20, 74, 41, 41, 48, 26, 58, 71, 72, 56) oricon_p <- c(9, 11, 8, 10, 12, 12, 7, 7, 9, 12, 9, 8, 14, 9, 9, 8, 8, 9, 9, 12, 10, 12, 7, 12, 9, 9, 12, 9, 10, 9, 13, 8, 15, 9, 10, 11, 17, 12, 9, 9, 11, 17, 11, 8, 10, 12, 13, 11, 12, 12, 16, 8, 7, 3, 11, 9, 10, 14, 15, 3, 11, 9, 9, 12, 3, 16, 9, 9, 16, 5, 9, 8, 3, 20, 9, 10, 8, 10, 8, 8, 12, 7, 20, 13, 8, 9, 12, 11, 9, 9, 3, 10, 14, 9, 12, 6, 12, 12, 18, 9) vocalo_c <- c(756, 578, 1489, 1630, 1170, 704, 864, 761, 2615, 990, 703, 495, 871, 763, 902, 897, 487, 615, 686, 1052, 649, 528, 465, 654, 793, 787, 849, 235, 605, 1240, 751, 730, 706, 708, 239, 556, 618, 747, 492, 1099, 322, 610, 430, 1109, 760, 609, 652, 668, 511, 1782, 810, 704, 651, 509, 553, 520, 654, 726, 982, 1318, 674, 666, 455, 822, 1071, 580, 675, 619, 1257, 477, 546, 789, 697, 1094, 823, 650, 892, 1082, 774, 414, 670, 1382, 975, 390, 588, 345, 812, 644, 980, 401, 610, 320, 654, 737, 674, 489, 799, 1019, 433, 1558) vocalo_l <- c(50, 44, 77, 69, 60, 56, 50, 49, 215, 56, 52, 36, 52, 79, 71, 74, 49, 40, 52, 60, 60, 27, 36, 64, 68, 63, 72, 17, 48, 99, 38, 60, 77, 76, 24, 34, 75, 40, 45, 53, 28, 49, 56, 70, 50, 54, 54, 63, 44, 70, 59, 62, 63, 38, 51, 52, 43, 67, 74, 57, 56, 47, 41, 48, 98, 48, 44, 51, 104, 43, 43, 39, 57, 106, 62, 61, 74, 84, 78, 40, 44, 65, 96, 27, 53, 22, 61, 48, 29, 32, 35, 33, 49, 39, 59, 52, 62, 51, 39, 88) vocalo_p <- c(11, 9, 13, 17, 11, 12, 10, 11, 38, 13, 10, 11, 12, 14, 15, 14, 12, 9, 14, 11, 13, 7, 11, 13, 14, 16, 23, 2, 9, 20, 9, 13, 15, 22, 6, 9, 25, 9, 8, 9, 7, 11, 17, 8, 10, 15, 14, 15, 10, 11, 13, 10, 15, 9, 12, 10, 9, 14, 18, 13, 15, 9, 6, 11, 8, 11, 9, 12, 21, 11, 8, 8, 12, 20, 15, 13, 14, 18, 24, 9, 10, 16, 35, 8, 11, 5, 12, 9, 6, 7, 9, 7, 9, 10, 12, 7, 13, 11, 10, 16) # 画面を消去し、グラフを描く frame() par(mfrow=c(1,3)) boxplot(oricon_c, vocalo_c) boxplot(oricon_l, vocalo_l) boxplot(oricon_p, vocalo_p)
箱ヒゲ図を描いてみました。
3つの図は左から、字数、行数、連数を表しています。それぞれ左がオリコン、右がボカロを表しています。
箱ヒゲ図の見方についてはなるほど統計学園高等部 | 箱ひげ図とかをご覧ください。昔は学校で習わなかったんですよね。
語数、行数、連数ともに、ボカロの方でひとつだけ突出している曲があります。これは『Music Wizard of OZ』という曲です。20分以上あるミュージカル仕立ての曲なので、その分だけ歌詞が長いのですね。
見ましょう♪
でもそれを除くと、ボカロもオリコンも字数に大した違いはないみたいです。いや、違いはあるって言えばあるけど…。大した違いと言えるのかは微妙です。
というわけで、平均値の差の検定をしてみます。さっきの1曲は外れ値として、対象から除くことにしました。まずは文字数について、分散の等質性を調査。
> var.test(oricon_c, vocalo_c)
すると
F test to compare two variances data: oricon_c and vocalo_c F = 0.7327, num df = 99, denom df = 98, p-value = 0.1242 alternative hypothesis: true ratio of variances is not equal to 1 95 percent confidence interval: 0.4923731 1.0898222 sample estimates: ratio of variances 0.7326778
と出ましたので、帰無仮説を棄却できません。なので、t検定ができます(で合ってますよね?)。
> t.test(oricon_c, vocalo_c, var.equal=T)
Two Sample t-test data: oricon_c and vocalo_c t = -1.4617, df = 197, p-value = 0.1454 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: -132.19659 19.65033 sample estimates: mean of x mean of y 694.0400 750.3131
有意水準5%で、帰無仮説を棄却できないことがわかりました。つまり、オリコンとボカロ曲の間で、字数には大して差がないということです。
同じように行数と連数について調べたところ、連数だけは5%水準で有意差があることがわかりました。
t.test(oricon_p, vocalo_p, var.equal=T)
Two Sample t-test data: oricon_p and vocalo_p t = -3.2554, df = 197, p-value = 0.001333 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: -3.0208291 -0.7415951 sample estimates: mean of x mean of y 10.24000 12.12121
なるほど〜。
オリコンの曲とボカロの曲は、文字数も行数もだいたい同じぐらいであるものの、連の数が異なり、ボカロ曲の方がわりと連数が多いことがわかりました。
どうしてでしょうか? 考えてみました。
私が思うに、ボカロ曲の複雑な構成と関係があるんじゃないでしょうか。
id:shiba-710さんがていねいに書いてくださっています。
ボカロ曲はオリコン曲と比較して構成が複雑で、曲調がどんどん変わっていきやすい傾向にあります。そのため、こまめに連に分けた表記が合うのではないかと思いました。
なお今回は私のR技術不足につき、グラフのデータをPDFに書き出してから見た目を調整しています。逆効果ではありませんように。
グラフの大きさとかは変えてないよ。
まとめ
今後の課題
- RubyとRのデータの橋渡しをかっこよくやりたい(今回はコピー&ペースト)。
- Rでもっとかっこよいグラフを描けるようになりたい。
- ボカロ曲の方が複雑な構成であることを示すために、時間あたりの文字数を比較することが有効だと思った。曲の長さを調べられるデータベースないかな。
形態素解析!
今度は、歌詞の内容に近づいてみます。引き続きRを使います。
いまから書く部分は、以前私が人力検索のクイズに回答したときと同じやり方をしているので、コードはそちらを見てください♪
最初に、オリコンとボカロの曲をそれぞれひとつのテキストファイルに統合しておきます。
で、RMeCabを使って形態素解析しました。実際には形態素じゃなくて単語と呼んだほうがいいと思うんですが、宗教上のタブーが絡むと聞いているので形態素解析と呼ばせていただきます。
oricon_best20 <- head(oricon_o[grep("代名詞", oricon_o$Info2), c(1:4)], 20) vocalo_best20 <- head(vocalo_o[grep("代名詞", vocalo_o$Info2), c(1:4)], 20) # 行の名前を消す(自動的に順位がつく) rownames(oricon_best20) <- NULL rownames(vocalo_best20) <- NULL # 表示 oricon_best20[c(1,4)] vocalo_best20[c(1,4)]
代名詞を頻度順にランキングさせてみました。こういう感じになります。
オリコン | ボカロ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
でも違うところもあります。私が注目したのは「みんな」です。オリコン曲にとても多く、ボカロ曲の3倍もあります。なんでだろう?
内容を見てみると、オリコン曲には2種類の「みんな」があることがわかります。
声を掛けるんだ↑(5位:AKB48『心のプラカード』)
みんなは言うけど
それができない
みんな積極的で↑(11位:乃木坂46『夏のFree&Easy』)
そう羨ましかった
みんな頑張って↑(51位:サザンオールスターズ『東京VICTORY』)
それ行け Get the chance!!
気取ったヤツは スルーでいいから↑(61位:2PM『ミダレテミナ』)
みんな勝手に Party in the club
ひとつめは、『心のプラカード』や『夏のFree&Easy』に見られる「みんな」です。「君」と「僕」の世界に終始しがちな世界にちょっと客観性を持たせるような位置付けになっているので、「脇役的みんな」と呼べます。
ふたつめは、『東京VICTORY』や『ミダレテミナ』に見られる「みんな」です。どちらも「みんな」自体が歌詞の中心であるように見受けられるので、「主役的みんな」と呼べます。
一方で、ボカロ曲はこんな感じです。
手をあわせてください↑(11位:ギガP『+♂』)
みんな元気にいただきます
ときにはみんなで 馬鹿騒ぎ↑(21位:れるりり『神のまにまに』)
裸踊りで 大笑い
さあ狂ったように踊りましょう↑(23位:れるりり『脳漿炸裂ガール』)
どうせ100年後の今頃には
みんな死んじゃってんだから
ボカロ曲には、「主役的みんな」が多く、はっきりした「脇役的みんな」はベスト100には見受けられませんでした。
オリコン曲はたくさんの人が協力して作っているのに対して、ボカロ曲はひとりでも作ることができます。聴くときのことを考えても、オリコン曲はテレビというみんなのメディアで多く流れるのに対して、ボカロ曲はスマホというプライベートなツールで多く視聴されている感じがします。
オリコン曲のほうがパブリックな印象があり、ボカロ曲のほうがプライベートな印象があります。
曲のテーマも、ボカロ曲は自分と向き合うことがテーマになりやすい傾向があると感じています。
こういうことから、「みんな」というのは自分との向き合いというよりも、少し視野の広い言葉なので、それがオリコン曲っぽさと相性がいいのかもしれないと、私は思いました。
「みんな」のほかに、「僕ら」という代名詞も、オリコン曲がボカロ曲よりもはるかに多く見られました。これにも同じような理由があるかもしれません。
まとめ
今後の課題
- 今まで黙ってたことがあるんですけど…。
歌詞って、その特性上、同じことばを繰り返すことがよくあります。でもその繰り返しの回数をどこまで厳密に表記するかにはブレがあります。同じことを歌っていても、「WOW〜」ってだけ書いてある歌詞もあれば「WOW WOW WOW WOW」って書いてある歌詞もあります。後者が前者の4倍に評価されてしまったら変な感じがします。
それに、今回はオリコン/ボカロの曲をすべてまとめてひとつのファイルにしてしまいました。その結果、1曲に50回出てくる単語も、50曲に1回ずつ出てくる単語も、同じ評価がされてしまっています。それはなんか変な感じ。重みのつけ方がわかりません…!
今後はそういうとこにも目配せできたらいいなと思います。
以上です。今日はここまで。私よくがんばりました♪
今回どうしてこういうことをしたのか、書いておきます。
私は歌詞を読むのが趣味です。アプローチ(ってほど大したものではないですが)は主にテクスト論に近いところです。
そんな感じで長いことこのブログをやってきましたが、なんだか閉塞感が見えてきました。読める歌詞は読めるけど、読めない歌詞はぜんぜん読めないな…って日々が続いたのです。
そんなころ出会ったのが
これです。ヤバいですこれ。
『進撃の巨人』という漫画を取り上げ、統計的な手法でキャラの名前とかを当てていくっていう企画です。私は衝撃を受けました。
だって、中身を読まなくても内容がわかるんだよ! これすげえよ!
そんなわけで、2014年のお正月から私は統計学のお勉強を始めました。いまはそれからちょうど1年ぐらいにあたります。なので、ここまでのお勉強のマイルストーンとしてこの記事を書きました。
ほんとだったら、ちゃんとオリコンとボカロを特徴付ける変数を見出せないとカッコ悪いし、それを説明変数にして、ランダムな曲たちをばっさばさと分類できる機械学習ができるべきでした。発表されたばかりのジャニーズ楽曲大賞とかネットの音楽オタクが選んだ2014年の日本のアルバムとか(まだ全曲ないけど)AKB48リクエストアワーとかを織り込めるぐらいの機動力をもてるべきです。
でも今の私にはできませんでした。今後に期待してください。これからもがんばります。
また、ココが間違ってるよ、もっとこうしたほうがいいよ、というご意見ありましたらお知らせください。もっと楽しい記事が書けるようになりたいです。
参考文献としては、以下の本が挙げられます。
Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例
- 作者: るびきち,佐々木拓郎
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/08/25
- メディア: 大型本
- この商品を含むブログ (4件) を見る
この本を片手に、Twitterのテキストマイニングをやってみたのが下記の記事です。
- 作者: 石田基広,小林雄一郎
- 出版社/メーカー: ひつじ書房
- 発売日: 2013/10/30
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
統計と検定については、いくつか本を読みましたが、今回一番長く手元に置いたのは、
- 作者: 山田剛史,杉澤武俊,村井潤一郎
- 出版社/メーカー: オーム社
- 発売日: 2008/01/25
- メディア: 単行本
- 購入: 64人 クリック: 782回
- この商品を含むブログ (69件) を見る
なお、私がこのブログを書くのに当たって全般で手元に置いている本は、
ここでご紹介しています。よければご覧ください。
最後にオマケをつけときます。オリコン曲とボカロ曲の歌詞の中から、形容動詞の語感になると判別された名詞のランキングです。
オリコン | ボカロ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
では、次回は、今までどおり、歌詞ひとつを取り上げてそれを読み込むタイプの記事を書きます☆ 2月5日ごろをお楽しみに!