StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

Spyder(PythonのIDE)を入れてみたところ、とても使いやすい

 Pythonを使う時、なんとなく、Canopy ExpressっていうIDEを使ってました。
 あまり深い理由はなく、オライリーの『Pythonによるデータ分析入門』でCanopy使いましょうって書いてあったり、IPythonのインストール解説ページにいくと、IPythonを使いたいなら単独で入れるのではなくAnacondaかCanopyのようにパッケージ化されたものを使うべしと書いてあった(リンク)からです。
 それで、とりあえず使えたから良いんですが、

  • スリープになるたびに不正終了としてエラーログをEnthoughtに送信するか聞かれる
  • デバッガを使おうとすると有料
  • スクリプトの特定部分を選択してその場で実行ってのができない(実行したい分だけコピペしてコンソールに貼れば実行できるけど、カーソルで選択した部分をcommand + enterで実行できる方がよい)


 など不満な点もあったので、いったんアンインストールして、試しにSpyderを入れてみたらえらく使いやすかったので、しばらくSpyderでいってみたいと思います。
 Python自体、超初心者なので、「乗り換え」みたいな大げさな話ではなく、色々試してみてる段階です。


 参考記事:
 いろいろ試した結果、pythonのIDEはSpyderに落ち着いた件--listen2the Silence


Canopyのアンインストール

 Spyderのインストールの前に、Canopyのアンインストールはやや注意が必要でした。手軽に削除というわけにはいかず、


 Uninstalling and resetting Canopy – Enthought Knowledge Base


 このページに書いてある手順を踏む必要があるようです。
 しかもこれはCanopyを一度アンインストールして再インストールしたい人向けに書かれてあるので、私はこの通りにやったわけでもないです。
 具体的には、上記ページの解説の「OS X」のところに従いつつもやや修正して、
 
 

  1. CanopyをPythonのデフォルトツールから外す
  2. Macを再起動
  3. Canopy.appを削除
  4. 「~/Library/Enthought/」「~/Library/Canopy」を削除。上記解説ページの手順だと、再インストールを想定してか、「Enthought」のディレクトリは残すと書いてあるけど、無視して根本から断ち切ることにした。
  5. 「locations.cfg」の削除も、「~/.canopy/」をまるごと削除することにした。
  6. ホームディレクトリにある「.bash_profile」「.profile」をテキストエディタで開いて、CanopyとかEnthoughtという単語が出てくるパラグラフを削除
  7. Macを再起動


 という順に作業しました。

AnacondaとSpyderのインストール

 AnacondaとSpyderを入れるのはかなり簡単でした。なお私はMacつかってて、OSはYosemiteです。


 Download Anaconda now! | Continuum
 ここでGraphical Installerってのをダウンロードしてインストールし、つぎに、


 Releases · spyder-ide/spyder · GitHub
 ここでSpyderをダウンロード・インストールしました。
 
 
 Spyderは、試しにPython2用のもの(Spyder-Py2)とPython3用のもの(Spyder)を両方インストールしましたが、ふつうに併用できるようです。併用してると、「このモジュールは2にはインストールしたけど3にはしてなかったっけ?」みたいな混乱が後々予想されますが。

じつはAnacondaだけ入れればOKだった。。。(2015.11.4追記)

 追記です。
 じつはAnacondaだけ入れておけば、その中にSpyderが埋め込まれているので、Spyder.appを別途インストール必要も、後述のようにインタープリタの紐付けを修正する必要もないことが分かりました。
 なので次のエントリで、いったん全部アンインストールして、Anacondaを入れなおしました。


statsbeginner.hatenablog.com



 従って、↓の内容はもはやあまり意味がないです。この方法でも使うことはできますが。


Spyderから動かすPythonインタープリタ等の変更

 1つ注意点があって、Mac版のSpyderを単にインストールしただけだと、全てが"Spyder.app"の中に梱包された材料で動くようです。
 理解して使う分には問題とまでは言えないでしょうけど、インタープリタはもちろん、ライブラリやモジュールも全部"/Applications/Spyder.app"の配下にブチ込まれているわけで、これは気持ち悪い。もともとインストールしてあったPythonとは無関係の世界がここに出来上がる感じです。せっかくこれまでpipとか使ってインストールしてたモジュール等はどうなんのと。
 というわけで、


 まずこの記事を参考に、
 Python Spyderで新しいモジュールが使用できない場合の対処法--listen2the Silence


 Preference > Console > Advanced settingsの一番上の項目で、Spyderが起動するPythonインタープリタを、もともと自分のMacに入れてたやつに紐付けしなおした(Spyder-Py2からはPython2、SpyderからはPython3を設定)。


f:id:midnightseminar:20151102123001p:plain


 次に上記参考記事では、PYTHONPATH managerでそれぞれのインタープリタが入っているディレクトリを指定しているのだが、これはべつにやらなくていいと思う。
 インタープリタを紐付しなおした時点で、次回起動時からはそのインタープリタに設定されてるPYTHONPATH(環境変数)やsys.pathが読み込まれるようになるので、いわゆる「pathは通ってる」状態で問題ないんじゃないだろうか。*1
 PYTHONPATH managerで何も追加しなくても、Spyder上のコンソールからsys.pathを確認してみると以下のようになる。

['', 
'/Users/ユーザ名/anaconda/lib/python34.zip', 
'/Users/ユーザ名/anaconda/lib/python3.4', 
'/Users/ユーザ名/anaconda/lib/python3.4/plat-darwin', 
'/Users/ユーザ名/anaconda/lib/python3.4/lib-dynload', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/Sphinx-1.3.1-py3.4.egg', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/setuptools-17.1.1-py3.4.egg', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/aeosa', 
'/Applications/Spyder.app/Contents/Resources/lib/python3.4/minimal-lib']


 一方、ターミナルから同じインタープリタを起動してsys.pathを確認すると、

['', 
'/Users/ユーザ名/anaconda/lib/python34.zip', 
'/Users/ユーザ名/anaconda/lib/python3.4', 
'/Users/ユーザ名/anaconda/lib/python3.4/plat-darwin', 
'/Users/ユーザ名/anaconda/lib/python3.4/lib-dynload', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/Sphinx-1.3.1-py3.4.egg', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/aeosa', 
'/Users/ユーザ名/anaconda/lib/python3.4/site-packages/setuptools-17.1.1-py3.4.egg']


 となっている。
 順番が若干違うのと、Spyderから見た場合は"Spyder.app"内のディレクトリが最後に加えられている。まずインタープリタのデフォルトのsys.pathを持ってきて、すこし改変して使わせているような感じなのだろうか。いずれにしても、内容的に、PYTHONPATH managerで何か追加しなければならないわけではないと思う。


 あとついでに、Preference > Console > Advanced settingsの中にあるPYTHONSTARTUPの設定というところで、"~/anaconda/pkgs/spyder-2.3.5.2-py34_0/lib/python3.4/site-packages/spyderlib/"という場所に置かれているscientific_startup.pyを設定しておきました。Pythonの起動時に自動的に読み込むモジュールとかが設定されてるやつです。
  Anacondaが提供してくれている、科学計算用のおすすめインポート一式という感じで、一部抜粋するとこんな感じ。

# Import modules following official guidelines:
try:
    import numpy as np
except ImportError:
    __has_numpy = False

try:
    import scipy as sp
except ImportError:
    __has_scipy = False

try:
    import matplotlib as mpl
    import matplotlib.pyplot as plt  #analysis:ignore
except ImportError:
    __has_matplotlib = False


 このほか、sysとかosなどもインポートされます。Pandasも入れたほうが良いような気がするので後で追記しようかな。
 AnacondaはPython3用にしかインストールしていないので、Spyder-Py2の方ではPYTHONSTARTUPの設定はそのままにしてますが、そのままだと"/Applications/Spyder-Py2.app"配下の何かを参照していて気持ち悪い面もあり、あとで調べて変更します。
 
 

Spyderを使う

 使えるようになりました。
 ↓のような3ペインで使い始めました。
 

f:id:midnightseminar:20151101174050p:plain

 
 たいへん打ちやすい気がします。
 この他、コーディングやファイル管理を支援する各種ツールのウィンドウを出すことが可能です。シェルについては、Python標準のシェルを操作する「Python Console」ってウィンドウと、IPythonを操作する「IPython Console」っていのが両方使えます。 

*1:PYTHONPATHやsys.pathについての説明はこの記事が分かりやすい。

【作業メモ】テキストファイルの行の先頭の記号を消したい(RとPython)

 すごい初歩的なテキストの処理作業のメモです。


 Pythonのクラスに関する↓の記事を読んでいたのですが、
 http://www.shido.info/py/python7.html


 この記事に書かれてある「code 1」「code 2」を自分でも実行してみようと思ってコピペしようとすると、各行の先頭に数字と記号がくっついてきます。


 こんな感じで。

09:     def Cls(cls=None, **key):
10:         """ making a new class"""
11:         key['class'] = cls
12:         return key


 これじゃコピペで実行できないので、この先頭の数字からスペースまでをきれいに削除して普通のコードにするための処理を、RとPythonで書いてみました。
 どう考えても手作業で消したほうが早いです。

Rできれいにする

 何か余計な文字列を消すという処理は、どうやるのが一般的なのかとかよく分かってないんですが、検索して空情報で置換するとか、要るところと要らないところにうまく分割して要らないほうを捨てるみたいなやり方を思いつきます。
 今回は、消すべき情報は全ての行の先頭に規則的に入っているわけなので、後者の方法でやってみます。


 上記記事のコードを「code_1.txt」「code_2.txt」というテキストファイルにコピペした上で、以下のとおり実行。

### ↓サイトのコードから冒頭の数字の部分とかを削除したい。
### http://www.shido.info/py/python7.html


# 作業ディレクトリを指定
setwd("作業ディレクトリのpath")

script.cleaning <- function (input, output) {
# 元記事のコードをテキストファイルにコピペしたものをinputに与える。
# outputとして指定したpath、ファイル名で、綺麗になったものが出力される。

file <- file(input, open= "rt", encoding="utf-8")  # ファイルを開く
lines <- readLines(file)  # テキストを読み込み
close(file)  # コネクションを切る

# 行の冒頭の数字のところと、コードの部分をsplitする。
# コロンと空白5つが挟まっているので、これを分割記号とする。
split <- lapply(lines, function (p) {
            unlist(strsplit(p, ":     "))
            })

# 分割された各行がリストで格納されており、それぞれのコードの
# 部分だけ取り出してベクトルに入れる。
vector <- c()
vector <- lapply(split, function (p) {
             vector <- c(vector, p[2])
             })

# NAとなる行(元のファイルにおける空行)を空要素に置き換える。
vector <- unlist(vector)
for (i in 1:length(vector)) {
	if(is.na(vector[i])==TRUE) {  # NAって他の方法でも指定できる?
		vector[i] <- c("")
	}
}

# ファイルを出力する。
output.file <- file(output, open="a")  # "a"は追記モード
writeLines(vector, output.file)
close(output.file)
}

# 実行する。
script.cleaning("code_1.txt", "R_output_1.txt")
script.cleaning("code_2.txt", "R_output_2.txt")


 できた。


f:id:midnightseminar:20151026154518p:plain


 しかしなんか回りくどいことをやってる気がする。
 次にPythonで書くときは、もっと普通な感じでforとかを使おうと思います。

Pythonできれいにする

 次はPythonでやります。Pythonの方は、スクリプトを保存してやってみます。
 同じく、上記記事のコードを「code_1.txt」「code_2.txt」というテキストファイルにコピペした上で、まず以下のスクリプトを「Script_Cleaning.py」というファイル名で保存。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# readlinesメソッドでやる場合

def cleaning(input, output):
    f = open(input)
    lines = f.readlines()
    f.close()
    for i in range(0, len(lines)):
        lines[i] = lines[i].split(':     ')[1]
    output_file = open(output, 'w')
    for line in lines:
        output_file.writelines(line)
    output_file.flush()
    output_file.close()


# 参考:readメソッドでやる場合

def cleaning_2(input, output):
    f = open(input)
    d = f.read()  # 行ごとではなく全体をう読み込み
    f.close()
    lines = d.split('\n')  # 改行ごとに分割
    for i in range(0, len(lines)-1):  # 1引かないとだめだった
        lines[i] = lines[i].split(':     ')[1] + '\n'  # 改行を足す
    output_file = open(output, 'w')
    for line in lines:
        output_file.writelines(line)
    output_file.flush()
    output_file.close()


 大きな意味はないですが2種類書きました。
 テキストファイルの一番最後のところで、改行コードの後が1行分になるかならないかの違いで、readメソッドのほうでは行数の指定をマイナス1しないとダメでした。


 このスクリプトを保存した上で、Pythonの対話環境下で、以下のように実行します。

>>> import Script_Cleaning
>>> Script_Cleaning.cleaning('code_1.txt','py_output_1.txt')
>>> Script_Cleaning.cleaning('code_2.txt','py_output_2.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "Script_Cleaning.py", line 11, in cleaning
    lines[i] = lines[i].split(':     ')[1]
IndexError: list index out of range


 あれ?
 code 1のほうは正常にファイルが出力されましたが、code 2のほうはエラーになった。
 行数が一致してないみたいな感じですが、元の記事をみてみると、code 2の48行目に改行が入っていて、ここで規則性が崩れたようだ。

46:                 w = 'writes programs'
47:     
48:             print '%s %s for %d, hours using %s on %s' %
                                                 (geta(self, 'name'), w, n, geta(self, 'language'), geta(self, 'OS'))
49:             
50:         workers = Cls() # dummy class


 よくみたらRでやった方も、code 2のほうはファイルは出力されてるけど、当該行の情報が消えていた。


f:id:midnightseminar:20151026154539p:plain


 めんどうなので、あとで考える。

Pythonの"Tweepy"でTwitter APIから取得したデータを読んでみる

[追記]
 このエントリを書いた後、レスポンスデータの読み方について別途詳しくエントリにまとめました。
 
www.statsbeginner.net
[/追記]

 前回のエントリで、PythonのTweepyライブラリを導入し、TwitterのAPIをいじることができるようになりました。

 statsbeginner.hatenablog.com

 
 で、APIから情報を取得して分析とかをするのであれば、Tweepyが返してくるデータから必要な項目を抜き出さなければならず、「Tweepyはいったいどんなデータを返してきているのか」を理解してなければ話にならないですよね。
 とにかくまずはTweepy公式サイトのReferenceを読んでいくべきですが、取得されるデータの内容について細かいことは書いてないような気が?
 どっかにドキュメントあるのかなと思ってGitHubをみてみたら、ソースコードとかは読めましたが、データモデルを解説する「model reference」というドキュメントは「To Do」となってて未作成でした。が、ソースコードのフォルダを開いて、理解できる範囲で眺めてみると、結構参考にはなりました。「models.py」というソースコードを開くと、クラス定義とかが書かれてあります。


 ひとまず理解のとっかかりとして、Pythonのインタラクティブシェル上で

>>> api.me()


 と打った時に取得されるデータの中身を読んでみました。なおこれは前回のエントリの続きで、APIクラスのインスタンスを持つ「api」というオブジェクトを生成してあることが前提です。
 APIクラスのインスタンスに.meというメソッドを適用すると、ユーザ自身についての情報がtweepy.models.Userという型(以下めんどうなのでUser型という)のオブジェクトとして返されます。
 この型のオブジェクトの中には、属性として色々な情報が埋まっている。長くてみるのが面倒なんですが、属性ごとに改行をはさみながら、以下に貼り付けてみます。マジマジと見つめると気持ち悪くなるのでざっと眺めるだけにしてください。

>>> api.me()
User(
 follow_request_sent=False, 
 has_extended_profile=False, 
 profile_use_background_image=True, 

 _json={u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'333333', u'default_profile_image': False, u'suspended': False, u'id': 257791390, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'profile_location': None, u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/6HCF2FYowE', u'indices': [0, 22], u'expanded_url': u'http://statsbeginner.hatenablog.com/', u'display_url': u'statsbeginner.hatenablog.com'}]}, u'description': {u'urls': []}}, u'followers_count': 1567, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'257791390', u'profile_background_color': u'C0DEED', u'needs_phone_verification': False, u'listed_count': 24, u'status': {u'lang': u'ja', u'favorited': False, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'contributors': None, u'truncated': False, u'text': u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', u'created_at': u'Wed Oct 21 23:24:41 +0000 2015', u'retweeted': False, u'in_reply_to_status_id_str': None, u'coordinates': None, u'in_reply_to_user_id_str': None, u'source': u'<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>', u'in_reply_to_status_id': None, u'in_reply_to_screen_name': None, u'id_str': u'656974416312598528', u'place': None, u'retweet_count': 1, u'geo': None, u'id': 656974416312598528, u'favorite_count': 0, u'in_reply_to_user_id': None}, u'is_translation_enabled': True, u'utc_offset': 32400, u'statuses_count': 11180, u'description': u'\u6771\u4eac\u3067\u30b5\u30e9\u30ea\u30fc\u30de\u30f3\u3084\u308a\u306a\u304c\u3089\u3001\u95a2\u897f\u306e\u5927\u5b66\u9662\uff08\u5de5\u5b66\u7814\u7a76\u79d1\u30fb\u535a\u58eb\u8ab2\u7a0b\uff09\u3067\u90fd\u5e02\u8a08\u753b\u306b\u95a2\u9023\u3059\u308b\u5fc3\u7406\u5b66\u306e\u7814\u7a76\u306a\u3069\u3092\u3057\u3066\u307e\u3059', u'friends_count': 1022, u'location': u'\u3064\u304f\u3070\u5e02', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'name': u'bata', u'lang': u'ja', u'profile_background_tile': False, u'favourites_count': 965, u'screen_name': u'statsbeginner', u'notifications': False, u'url': u'http://t.co/6HCF2FYowE', u'created_at': u'Sat Feb 26 04:59:47 +0000 2011', u'contributors_enabled': False, u'time_zone': u'Tokyo', u'protected': False, u'default_profile': True, u'is_translator': False},

 time_zone=u'Tokyo', 
 suspended=False, 
 id=257791390, 
 
 description=u'\u6771\u4eac\u3067\u30b5\u30e9\u30ea\u30fc\u30de\u30f3\u3084\u308a\u306a\u304c\u3089\u3001\u95a2\u897f\u306e\u5927\u5b66\u9662\uff08\u5de5\u5b66\u7814\u7a76\u79d1\u30fb\u535a\u58eb\u8ab2\u7a0b\uff09\u3067\u90fd\u5e02\u8a08\u753b\u306b\u95a2\u9023\u3059\u308b\u5fc3\u7406\u5b66\u306e\u7814\u7a76\u306a\u3069\u3092\u3057\u3066\u307e\u3059', 
 
 _api=<tweepy.api.API object at 0x1026bfe50>, 
 verified=False, 
 profile_location=None, 
 profile_image_url_https=u'https://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', 
 profile_sidebar_fill_color=u'DDEEF6', 
 is_translator=False, 
 geo_enabled=True, 
 
 entities={u'url': {u'urls': [{u'url': u'http://t.co/6HCF2FYowE', u'indices': [0, 22], u'expanded_url': u'http://statsbeginner.hatenablog.com/', u'display_url': u'statsbeginner.hatenablog.com'}]}, u'description': {u'urls': []}}, 
 
 followers_count=1567, 
 protected=False, 
 id_str=u'257791390', 
 default_profile_image=False, 
 needs_phone_verification=False, 
 listed_count=24, 
 status=Status(contributors=None, truncated=False, text=u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', in_reply_to_status_id=None, id=656974416312598528, favorite_count=0, _api=<tweepy.api.API object at 0x1026bfe50>, source=u'Twitter Web Client', _json={u'lang': u'ja', u'favorited': False, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'contributors': None, u'truncated': False, u'text': u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', u'created_at': u'Wed Oct 21 23:24:41 +0000 2015', u'retweeted': False, u'in_reply_to_status_id_str': None, u'coordinates': None, u'in_reply_to_user_id_str': None, u'source': u'<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>', u'in_reply_to_status_id': None, u'in_reply_to_screen_name': None, u'id_str': u'656974416312598528', u'place': None, u'retweet_count': 1, u'geo': None, u'id': 656974416312598528, u'favorite_count': 0, u'in_reply_to_user_id': None}, coordinates=None, entities={u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, in_reply_to_screen_name=None, id_str=u'656974416312598528', retweet_count=1, in_reply_to_user_id=None, favorited=False, source_url=u'http://twitter.com', geo=None, in_reply_to_user_id_str=None, lang=u'ja', created_at=datetime.datetime(2015, 10, 21, 23, 24, 41), in_reply_to_status_id_str=None, place=None, retweeted=False), 
 
 lang=u'ja', 
 utc_offset=32400, 
 statuses_count=11180, 
 profile_background_color=u'C0DEED', 
 friends_count=1022, 
 profile_link_color=u'0084B4', 
 profile_image_url=u'http://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', 
 notifications=False, 
 favourites_count=965, 
 profile_background_image_url_https=u'https://abs.twimg.com/images/themes/theme1/bg.png', 
 profile_background_image_url=u'http://abs.twimg.com/images/themes/theme1/bg.png', 
 screen_name=u'statsbeginner', 
 is_translation_enabled=True, 
 profile_background_tile=False, 
 profile_text_color=u'333333', 
 name=u'bata', 
 url=u'http://t.co/6HCF2FYowE', 
 created_at=datetime.datetime(2011, 2, 26, 4, 59, 47), 
 contributors_enabled=False, 
 location=u'\u3064\u304f\u3070\u5e02', 
 profile_sidebar_border_color=u'C0DEED', 
 default_profile=True, 
 following=False
 )


 頭がクラクラしてきます。
 でもこうやって眺めてみると、全体の構造と、だいたいどういう情報が取り出せるのかってのがわかりますね。
 全体としてはUser()というくくりになっていて、このカッコの中に「follow_request_sent=False」のように「名前=値」の形式で属性が埋め込まれている。
 内容は属性の英語名称から想像つくものが多いです。内容が文字コード表示になってて分かりづらいところを言うと、descriptionというのは自己紹介文であり、statusはたぶん最新のツイートかな。

 なおいたるところに出てくるu'...'という書式はPythonにおけるUnicode文字列ってやつで、uは記号として無視しておけばいいです。


 よくみると、属性の中にはすごく長いものがありますね。たとえば「_json=」というやつはTwitterから返ってきた生のJSONのデータなんだろうか?
 JSONは一般的に、{"キー":値, "キー":値, "キー":値}という情報のセットになっていて、これはPythonでいうと辞書型の記述方法に相当します。実際、このUser型に埋め込まれている_jsonという属性の中身を取り出すには、辞書型としてアクセスすれば良いようです。

>>> me = api.me()  # 自分についての情報を持つUser型のオブジェクトをつくる
>>> type(me)  # 型をみてみる
<class 'tweepy.models.User'>
>>> me_json = me._json  # JSONが埋まってる属性を抜き出す
>>> type(me_json)  # 型をみてみる
<type 'dict'>
>>> me_json['followers_count']  # フォロワー数を意味するキーを指定してみる
1567


 上記のようにするとフォロワー数が取得できるわけですが、もちろん、フォロワー数を取得するためにこのJSONの中まで見に行く必要はない。
 User型の属性の中に直接的に「followers_count=1567」というのがあるわけだから、

>>> me.followers_count
1567


 と書けばいいわけです。当然、

>>> number_of_followers = api.me().followers_count
>>> print(number_of_followers)
1567


 というような書き方でも成り立つ。
 ただし、TweepyのAPIクラスインスタンスはメソッドが適用されるたびにTwitter APIにリクエストを投げるものなので、上記のようにapi.なんたらと書くたびに通信が発生して時間がかかるので、プログラムを書くときはこのapiが何回も登場するとヤバい。
 User型の属性情報に何度もアクセスするのであれば、最初に書いたみたいに一旦「me」というようなオブジェクトをつくって値を渡してから中身を掘ったほうがいいと思う。


 あとでstatus型など他のデータも中身をしっかり読んでみようと思いますが、だいたい同じような勝手で読み解いていけばいいんじゃないでしょうか。

Tweepyを使って、PythonでTwitterのAPIを超簡単に操作する

PythonでTwitterのAPIを触りたい

 PythonでTwitterのAPIを操作し、検索の自動化による情報収集・解析とか、自動でつぶやくbotの作成を可能にしたい。
 そこでTweepyというライブラリを使うと、とても簡単に態勢が整いました。10行以内のコードで準備が終わります。
 Tweepy


 ちなみにTwitterのAPIは「REST API」と「Streaming API」の2種類があって、今回使うのはREST APIのほうです。REST APIは(RESTというものの説明はREST - Wikipediaでもみてください)、HTTPリクエストを投げるとそれに対応した情報がjsonで送られてくるってやつで、特定のツイートやユーザの情報を取得したり、検索をしたり、つぶやいたりするのに使うものです。Streaming APIはTwitter上に流れる情報がリアルタイムに流れ込んでくるもので、ビッグデータ的な何かをやる特殊な人たち向けのもののようです(解説はこの記事とかこのスライド)。

  
 Tweepyの使い方の解説サイトはたくさんあり、私はたとえば
 PythonでTwitterAPIを使う。その2 - WhereToStart
 とかを参考にコードを書き始めました。しかし上記ページのコードでは一部動かないところもあったし、後述のとおりACCESS TOKENの取得は自動化せず直接記述してもいいので、Tweepyの公式サイトなども合わせて見ながらコードは改変していった。結果的にはほぼ公式サイトに載っている内容に落ち着きました。


 以下に導入手順をメモしてますが、今回はMac(Yosemite)のターミナルから操作してます。Pythonのバージョンは2.7.10です。

各種認証・認可キーの取得

 事前準備として、Twitterの公式サイトでアプリ開発者としての登録を行って、各種キーを取得しておく必要がある。
 これは以前、RからTwitterAPIをいじるためのtwitterRを試した時のエントリの「Twitterアプリ作成の登録を行う」という項に書いてある。
 RでTwitterのデータを分析するための準備(2015年6月現在のやり方) - StatsBeginner: 初学者の統計学習ノート
 
 
 簡単におさらいしておくと、

  1. まずツイッターアカウントに電話番号を紐づける。(このページから)
  2. 開発者用ページにいって新たなアプリを作成し、名前とかを付ける。(このページから)
  3. Keys and Access Tokensタブで、「Consumer Key」「Consumer Secret」を確認し控える。
  4. 下の方の「Token Actions」のところの「Create my access token」を押して、出てくる「Access Token」「Access Token Secret」を確認し控える。

 
 
 後述しますが、4のところは自分のTwitterアカウントで開発者になると同時に自分のアカウントへの操作権限を付与してもらおうとしているため、ここでボタン押すだけでアクセストークンがもらえるわけですが、このアプリを色々な人(色々なアカウントの保有者)が利用するという場合、その人ごとにTwitterに対して自分のアカウント用のAccess Tokenを要求するという手続きになります。


 ここで、なんたらキーとかが色々でてきてややこしいんですが、この辺のページを読むと何となく分かる気がします。OAuthの理解のために、読んでおいて損はないと思う。
 OAuthの鍵がたくさん登場するが各々の役割についてまとめてみた - 知のレバレッジを最大化せよ
 暇なメモ帳: OAuthとは何か?


 Consumerというのは要するにアプリのことであり、Consumer Key及びConsumer Secretは、「そのアプリの開発者として持っておく鍵」。
 Access TokenとSecretは、「そのアプリで操作したいTwitterアカウントの保有者として持つ鍵」であり、これを持っているとそのアカウントに自由にアクセスして操作することが可能になる。
 今回自分で作成しているアプリから自分のTwitterアカウントを操作する分には、開発者ページにおいてこの両方を一気にもらうことができるということです。
 他の人がこのアプリの利用者(User)として自分のTwitterアカウントに対しAccess Tokenを求める場合で言うと、

  1. Userが認証要求するたび毎にTwitterがRequest Tokenを発行してアプリに返してくれる。
  2. アプリはこれを用いたHTTPリクエストの手段をUserに提供する(例えばリンクを生成する等)。
  3. このリンク等によってUserが、Request Tokenを引っさげた状態でTwitterのサイトにアクセスし、「自分のアカウントにアクセスするためのAccess Tokenをくれ」と要求する。
  4. するとTwitterは、Access Tokenを要求しているUserに対して、要求先アカウントのID・PW入力を求める。IDが「どのアカウントを操作したいのか」を示し,PWが「そのアカウントの所有者である証拠」を示すわけ。
  5. PWが正しければ確認用のコード(Verifierと呼ばれる)が画面上で与えられるのでそれを控え、このコードをアプリに入力する。
  6. アプリがこのコードとRequest TokenをセットでTwitterに投げ、Twitterからそのアカウント用のAccess Tokenを受け取る。
  7. これにより、「あるアプリ」から「あるアカウント」にアクセスする権限が認められたことになる。
  8. Access Tokenはアプリが保管しており、UserがそのアプリからAPIを通じて自分のアカウントを操作する際はいつも、アプリ固有のConsumer Key及びConsumer Secretと、自分のアカウントから発行されているAccess Token及びAccess Token Secretという4つのキーを持って、アクセスを要求することになる。


 (・・・というような概略で理解したんだけど正確かどうかは怪しいので間違ってたらご指摘ください。)

TweepyのインストールとAPI操作の準備

 さて次にTweepyのインストールですが、これはとても簡単。
 easy_installでやる場合は、Macのターミナルから、

$ sudo easy_install tweepy
Password:


 これで終了。easy_installではなく「pip install tweepy」でもよい。各自のパッケージ管理ツールの使い方次第*1
 そして、次のようなコードを書いたスクリプト(テキストファイルに記述して拡張子を.pyにしたもの)を用意して保存しておく。一番上の行は環境によって違うかも。
 もちろん、スクリプトを用意せず対話環境下で一行ずつ打ち込んでも大丈夫。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

# Tweepyライブラリをインポート
import tweepy

# 各種キーをセット
CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
ACCESS_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)

#APIインスタンスを作成
api = tweepy.API(auth)

# これだけで、Twitter APIをPythonから操作するための準備は完了。
print('Done!')


 XXXXX...のところは、自分でTwitter開発者ページから取得したキーを入れます。
 ここでやってることは、

  1. "CONSUMER KEY"と"CONSUMER SECRET"をセットして、OAuthHandlerクラスのインスタンスを作成する。authというオブジェクトがそれ。
  2. OAuthHandlerインスタンス(auth)に、"ACCESS TOKEN"と"ACCESS TOKEN SECRET"をセットする。
  3. セットが終わったOAuthHandlerインスタンスから、TweepyのAPIクラスのインスタンスを作成する。apiというオブジェクトがそれ。


という流れ。
 私はこのスクリプトの名前を"twpy.py"としたので、Pythonの対話環境下で該当のディレクトリに行き*2

import twpy.py


 と書いてこれを呼び出します。すると"twpy.api"という名前のAPIクラスのインスタンスが作成されるので、あとはこのtwpy.apiにいろんなメソッドを適用することで、Twitter APIを操作することができます。*3


 なお、以下では"twpy.api"と打つのがめんどくさかったので、

api = twpy.api


 としてから「api」というオブジェクトを操作するようにしてるのですが、大きな意味はありません。

参考:ACCESS TOKENの入手の仕方

 先ほどのコードでは、4つのキーを開発者ページから事前に取得して上記の通り直接スクリプトの中に記述しましたが、OAuthの認証・認可手順としては、アプリのCONSUMER KEYからスタートして、認証用コードの入力などの手続きを挟んで操作したいアカウントにひも付け、ACCESS TOKENを取得するほうが普通の流れではあります。
 アプリ登録を行った開発用アカウントとは別のアカウントも操作したい場合は、そのようにしておいて切り替えられるようになってたほうがいいです。
 
 
 これは冒頭の解説ページを参考にしましたが、スクリプト内に以下のように書いといたらできた。
 上記コードの「ACCESS_TOKEN = ...」と「ACCESS_SECRET = ...」の行を消して以下のコードに置き換えれば、スクリプトを実行した際、ターミナル上にAccess Token要求用のURLが出力されるので、それをコピペしてブラウザに入力。Twitterアカウントへのログインが求められるのでID・PWを入力し、確認用コード(Verifier)が表示されたらそれを控えてターミナルに戻って入力すると、裏でAccess Tokenを取得してセットが行われます。

# 以下は、アクセストークンをもらいにいく場合のコード。
# まず、認証コードを貰いに行くアドレスを取得する
redirect_url = auth.get_authorization_url()

# アドレスを表示し、ブラウザでアクセスして認証用コードを取得してくる。
# べつにGetなんたらはなくてもよく、redirect_urlをprintするだけでもよい。
print 'Get your verification code from: ' + redirect_url

# ブラウザから取得してきた認証用コードを対話モードで入力する。
# strip()はコピペの際に末尾に改行コードとかスペースが入ったのを消すため。
verifier = raw_input('Type the verification code: ').strip()


# Access TokenとAccess Token Secretを取得してそれぞれオブジェクト
# として格納しておく。
auth.get_access_token(verifier)
ACCESS_TOKEN = auth.access_token
ACCESS_SECRET = auth.access_token_secret

 
 

Tweepyを使ってみる

 これで準備完了でTweepyが使えるようになったわけですが、具体的に何ができるようになったかというと、
 API Reference — tweepy 3.3.0 documentation
 このTweepy公式のReferenceに書かれているように、自分のTwitterアカウントに対する各種操作をPythonから実行できるようになったということです。
 できることは色々あるので、ここではためしに実行してみた簡単な例だけ載せておきます。
 ターミナル上での出力結果も書いているので、「>>> 」等の記号も含めてコピペしたものを掲載してます。


 たとえば現在の自分のタイムライン上の最新のツイートを参照するには、

>>> print api.home_timeline()[0].text
RT @estzet: 【宣伝】「美味兎屋」 玄関のドアをさらりと潜れば、漸く近づくその空間。 望み望まぬ明るい闇へ。 ゼロ割のごとき空間へ。 立方体の地平面。 常識ルールの無意味な世界。 ようこそ、みなさん、美味兎屋へ。 https://t.co/DUaMqNSdTM #na…


 と書けばよい。[0]はリストで返ってきている結果の1個目ということ。
 この書き方だとつぶやきの内容だけが表示されるんですが、これは末尾で.textという属性を指定しているからです。
 たとえば、

>>> api.home_timeline()[0]
Status(contributors=None, truncated=False, text=u'RT @syufutosousaku: \u3010\u5ba3\u4f1d\u3011\n\u54c0\u308c\u306a\u308b\u304b\u306a\u3001\u771f\u5b9f\u3092\u898b\u629c\u3051\u306c\u8005\u3088\u3002\u5831\u308f\u308c\u308b\u3053\u3068\u306e\u7121\u304d\u9ab8\u3088\u3002\n\u3010\u306e\u3056\u3089\u3057\u3011\nhttps://t.co/tKKyY6ZAxq\n\u9ad1\u9acf\u304c\u3057\u3083\u3079\u3063\u305f\u308a\u3001\u6b7b\u4f53\u304c\u52d5\u3044\u305f\u308a\u3059\u308b\u63cf\u5199\u304c\u304a\u5acc\u3044\u306a\u65b9\u306f\u3054\u6ce8\u610f\u3092\u3002', is_quote_status=False, in_reply_to_status_id=None, id=656681903483781120, favorite_count=0, _api=<tweepy.api.API object at 0x102eaedd0>, source=u'Tabtter Free', _json={u'contributors': None, u'truncated': False, ...途中省略...)


 と打ってみるとわかるように、もともとは1つのツイートに対してめちゃめちゃ長い情報が存在しています。上記はコピペは途中から省略していて、実際にはこの場合、1件のツイートに対して半角34,000字分ぐらいの情報が返ってきてました……。
 さっきはこの、1件のツイートに付与されている大量の情報の中の、textという属性だけを呼び出したということになります。
 属性の指定によって、ふぁぼの有無とか、リプライならその宛先はどこかとか、ツイートの位置情報とかを、個別に取得することもできます。
 
 
 次に、自分のアカウントでつぶやくには、たとえば、

>>> api.update_status(status='オッス! from Tweepy')  


 と書けば良い。


 f:id:midnightseminar:20151021132250p:plain


 このへんを応用すれば、botがつくれるわけですね。TwitterのAPIではつぶやきをstatusと呼んでいるようです。ちなみにフォローは「create_friendship」で,アンフォローは「destroy_friendship」(笑)と言うみたい。


 ツイートを検索する場合は、たとえば「安倍」を検索ワードとすると、

>>> search_result = api.search(q='安倍')


 と書けばいい。このsearch_resultに.textというメソッドを適用すると、ヒットしたツイートの文面が出力される。
 デフォルト設定だと15件結果が格納されているので、forで順番に出力してみます。

>>> search_result = api.search(q='安倍')
>>> for result in search_result:
...     print result.text
... 
RT @hivere: 仲良しの安倍自民党も同じく、党是は「国民の生存権弾圧」@naniwacchiやっぱり大阪維新の会の党是は「生存権弾圧」なんだよ。「
 @12koku
経済が失速すればするほど、経済経済と叫ぶ安倍政権が支持される。誰が失速させたが問われないのが現状。楽して儲かる軍需産業へ傾斜するのもわかる。先は地獄でも進まなくてはならないと言われて勇ましいと思う人が一定数いるのは事実。「敗戦後、次は勝つぞと、願う馬鹿。」
RT @gogoichiro: 異例というより、この国の政治史において初でしょう。つい先日まで、共産党は安倍自民党は子供達を戦場に送る、戦争法案を命がけで廃案にすると言ってたのに、大阪では安倍自民党より維新を潰さなければならないと自共共闘となる。 https://t.co/XS…
RT @tokyoseijibu: 第3次安倍改造内閣で新設された一億総活躍担当相は一体何をするのか政権内からも困惑の声が上がっています。https://t.co/NMWWqwEiz1
RT @alres9: @21remon @Aahi69Mai 安倍さんに媚びないんじゃなくて、権威に媚びてないんですけど、違いが分からないようです(笑)
RT @kitanihonganba: 「諸君とともに、先頭に立って全力を尽くす」〜観艦式で安倍総理が訓示http://t.co/xAlcTjvXrU

諸君の後ろには、常に諸君を信頼し、諸君を頼りにする日本国民がいます
私と日本国民は、全国25万人の自衛隊と共にある http…
https://t.co/l16D9Uftq3 宮根、辛坊、そして1位はやっぱりあの人…安倍政権と安保法制を後押しした“戦争協力者”ランキング5位〜1位
RT @kei_nakazawa: 10月24日(土)18時30分から。東京堂で中野晃一さんとお話します。ヘイトスピーチを軸に見た安倍政権についたて。詳細とお申込みはこちら → https://t.co/T0CdoKRsWQ
#society #jounalistic
 安倍政権に代わる枠組みを/TBSラジオ小池氏が主張 野党結束が大事/民主・維新の各議員と議論 - しんぶん赤旗:
 日本共産党の小池晃副委員長は19日、TBSラジオの.. https://t.co/WbCTxCzwPs
RT @ROCKERS_channel: 日本で人気の4人グループ「T.O.K.」解散へ。安倍首相の訪ジャマイカ晩餐会出席後すぐ。 https://t.co/JJJ6Zu0sJT

結成から23年。実は8年前からメンバー内の人間関係に問題を抱えていたんだ……。 https://…
【日本も乗り遅れるな!】カナダ総選挙で野党自由党が圧勝!TPPには慎重姿勢!世界中で止まらない反安倍的な流れ!  https://t.co/t2DazXjIow
RT @renho_sha: 怒りを覚える。
24年度補正で1591725年度補正で12231億
安倍内閣が基金に支出した税金。その大半が年度内に使われす、埋蔵金化、バラマキに使われているものもある中、子どもの貧困対策は民間支出の基金?政府が予算確保すべきだ
https:/…
RT @SatoMasahisa: 【これはひどい、「安倍政権批判の文言入り文具、有無を調査 北海道の学校 」
教育現場に「アベ政治を許さない」文字のクリアケース、北海道教育委員会は政治的中立性上問題、一方教職員組合側は教育現場を萎縮させると反発。組合側の常識を疑う
http:…
誤った政策が招いた好ましくない結果を他人のせいにする無責任さには今更ながら呆れるばかりだ。経済政策ブレーンの浜田宏一(イェール大学教授)なども無責任さでは安倍にヒケを取らない。
政治家や政策立案者などが無責任なのは、国民が無責任だから?
次回の国政選挙で、そうでないことを示そう。
RT @ishincheck: 考えが合わない人同士がいる部屋にゴキブリが出てきたら、
その時ばかりは協力して退治しますよね。
そういう事です。
@gogoichiro 大阪では安倍自民党より維新を潰さなければならないと自共共闘となる。


 物騒なツイートがたくさん返ってきました。


 とりあえず動かせるようになったので、これから色々試してみます。
 ここから先はとにかく、
 API Reference — tweepy 3.3.0 documentation
 このReferenceをみながら、やりたいことを探して操作してみればいいですね。必要に応じてTwitterのAPIの仕様も参照しながら(ツイートを取得する件数を指定するときのページ数みたいな概念がまだよくわかってなかったりする)。


 基本的には、行いたい操作に対応するメソッドをひたすら「api」に適用していくだけ、つまり「api」のあとに「.なんたら()」を付けて、カッコの中に引数を入れていくだけでTwitterへのリクエストが発射できるので、難しいことはあまりないと思います。
 レスポンスとして返ってくるデータ(JSONで返ってきているらしい)から必要な情報を指定して抜き出すことさえできれば、あとはそれを形態素解析するなり単語の頻度を集計するなりRT回数を調べるなり、好きにすればいいということです。
 

 たとえば特定のツイートの内容を取得するのであれば、get_statusというメソッドを適用し、引数にIDを入れればよく、返ってくるデータのうちどの属性を表示したいかをまた「.属性名」と書いて指定する。
 こんな感じで。

>>> print api.get_status(656974416312598528).text  # ツイート本文を表示
スプラトゥーンはやばい。廃人になってしまうので博論提出までは控えるようにしてる。
>>> print api.get_status(656974416312598528).retweet_count  # RT件数を表示
1
>>> 


 返ってくるデータの読み方は後日まとめました。

www.statsbeginner.net

*1:パッケージ管理ツールについてはここを参照 http://www.yunabe.jp/docs/python_package_management.html

*2:Python起動前に移動しててもいいし、Pythonの対話環境ではosモジュールをインポートしてos.chdir('パスを記述')とすればよい。

*3:もちろん、スクリプトにどんな名前をつけ、どこに保存し、どうやって呼び出すかとかは、各々の判断で行うもの。対話環境で呼び出しているのも単に今回試してみる上で私がそうしているだけであり、別のスクリプトから呼び出してもよい。

RMeCabで単語に品詞を振る作業

 前回のエントリで書いたように、外国人向け日本語教科書に登場する単語の分析をしている友人の作業を手伝うために、形態素解析エンジンMeCabのインストールと、RMeCabのインストールを行いました。
 今回は、その手伝い作業の内容について書いておきます。


 その友人がやりたがっていた作業は何なのかというと、まず


f:id:midnightseminar:20151005222824p:plain


 こんな感じで単語が縦に並んだCSVファイル(1列なのでカンマは出てこないですが)が20個ぐらいあって、それぞれの単語に対し、品詞を判定して横に入力したいとのこと。つまり実際には形態素解析と言っても分かち書きをやりたいわけではなく、単に品詞が分かればいい。
 判定対象の単語は全部で述べ3万8000ぐらいあるので、手作業は厳しいので、MeCabに判定させようと思います。
 なおデータをよく見ると、1行1単語になってなくて複数単語になってしまってる行があったり、カッコなど無駄な記号が入ってる行とかもあって綺麗なデータとはいえず、分かち書きも結局必要になりました。


 さて実際の処理ですが、Rから実行することとします。
 内容は単純で、RMeCabパッケージのRMeCabC()という関数を使います。この関数にテキストを渡すと、テキストを単語に分解した上で、要素名に品詞名を持った単語のリストが返ってきます。

> library(RMeCab)
> RMeCabC("アジアけんきゅう")
[[1]]
    名詞 
"アジア" 

[[2]]
  動詞 
"けん" 

[[3]]
    名詞 
"きゅう" 


 こんな感じで、リストの要素名称が品詞になっていて、要素として単語が入っています。
 なお、上の結果をみると「けんきゅう」が2語に誤って分かれてしまっていますね。元のデータは、外国人向けの日本語教科書なのでひらがなが多いんですが、ひらがなばかりの文字列に解析をかけると誤認識が多くなるようです。
 またそもそも、単語に分けた一覧表を解析してるので文脈からの判断ができないと思われ、もとの文章のまま文を単位として解析したほうが精度が高い気もします。


 しかしまぁ、友人はなぜか上記のようなデータを作ってたので、仕方ない・・・。とにかく、このデータの横に、自動的に判定された品詞を付けてデータを返すことにしました。先にアウトプットのイメージを貼っておきます。


f:id:midnightseminar:20151005225332p:plain


 こんな感じで、Originalという列にもともとの解析対象となった単語が並んでおり、それぞれ形態素解析をかけて、Originalの横に単語と品詞が表示されるようにします。1行が1単語じゃないものについては、単語→品詞→単語→品詞...というふうに表示されるようにします。精度低いですねw


 書いたコードの主要部分だけ貼り付けておくと、以下のような感じです。

# 元のcsvファイルを読み込んで、単語の一覧表をベクトルに格納する関数を記述。

read.source <- function(path) {
# pathのところにcsvファイルのパスを記述すると、単語一覧をベクトルとして返す。
	t <- read.csv(path, header=TRUE)
	t <- t[,1]
	t <- as.character(t)
	return(t)
}


# 単語一覧のベクトル(入力ベクトルと呼んでおく。)を与えると、1個1個に
# MeCabの解析をかけた結果を返す関数を記述する。
# ただし入力ベクトルは、単語一覧といいながら複数の単語が混じっている要素
# もあるので、それぞれ分かち書きを行った上で、単語、品詞、単語、品詞、、、
# と横に並べることにする。


get.pos <- function(vec) {
# vecに入力ベクトルを与えると、最終的に元の単語と品詞(複数の単語に分解
# されるものは単語→品詞→単語→品詞...と横に並ぶ。)を1行ずつまとめた
# データフレームが出力されるような関数をつくる。
# 品詞は英語でpart of speechというらしいので、get.posにしておいた。


# まず、入力ベクトルの各要素にRMeCabC関数で形態素解析を行い、各要素の
# 解析結果を1要素とするリストを返す。
# ただし、RMeCabC関数はそれ自体がリストを返すので、unlist()でいったん
# ベクトルに変換している。
list <- lapply(vec, function(p){unlist(RMeCabC(p))})


# 入力ベクトルの要素の中には、5つ、6つの単語に分かち書きされてしまう
# ものがある。アウトプットのテーブルには、分かち書きの最大個数分だけ列の
# 数が必要なので、最大で何語に分離されてるかを確認。
elements.length <- c()
for (i in 1:length(list)) {
	elements.length <- c(elements.length, length(list[[i]]))
}
max.element.length <- max(elements.length)


# 最終的に、単語、品詞、単語、品詞と並ぶようなテーブルを出力したい。
# そのためまず、入力ベクトルの要素数を行数とし、分かち書き個数が最大の
# 要素の要素数を列数とする行列をつくり、入力ベクトルの各要素に形態素解析
# を行って、1行1行入れていくことにする。
# 分かち書き個数が最大より小さい行については、後ろに空の要素を追加して、
# 長さをそろえる。


# 上のリスト要素1個1個を、単語・品詞・単語・品詞...という順番に並び
# 替えてベクトルを返す。
get.row <- function(element){
	names <- names(element)
	row <- c()
	for(i in 1:length(element)) {
		row <- c(row, element[i], names[i])
	}
	names(row) <- NULL  # 要素名を消す
	
	# 最大長より短いものについて、足りない分を空の要素で埋める。
	if (length(row) < (max.element.length * 2)) {
	for (i in 1:((max.element.length * 2)-length(row))) {
		row <- c(row, "")
	}
	}
	return(row)
}


# 結果をリストにまとめる。
# このリストの1要素1要素が、最終的なアウトプットの1行1行になる。
rows.list <- lapply(list, get.row)


# アウトプット用に行列をまとめる。空の行列を作って、要素を置き換えていく。
output.matrix <- matrix(nrow=length(rows.list), 
                        ncol=(max.element.length * 2))
for (i in 1:length(rows.list)) {
	output.matrix[i,] <- rows.list[[i]]	
}


# 最終アウトプットをデータフレームで得る。
output.df <- as.data.frame(cbind(Original=vec, output.matrix))


# 列に名前をつける。
colnames <- c()
for (i in 1:max.element.length) {
	colnames <- c(colnames, "Word", "POS")
}
colnames(output.df)[2:((max.element.length * 2)+1)] <- colnames

# ふつうにrep()でつくればよかったな

return(output.df)
}



# 実行用の関数

parse.words <- function(path) {
	vec <- read.source(path)  # ファイルを読み込んでベクトルに変換
	output <- get.pos(vec)   # 解析して出力用DFを得る
	return(output)
}


 このあとは20個ぐらいある解析対象のcsvファイルのパスを最後のparse.words()に渡し、作成されたデータフレームをまたwrite.csv()でcsvとして書き出しました。
 精度は低いですが、後は自分で何とかしてくれと言って、友人に渡しました。

形態素解析エンジンMeCabの導入と、R及びPythonからの利用(Macの場合)

MeCabで形態素解析

 外国人向けの日本語教育の研究をしてる友人がいて、海外での教材出版などを私も共著者として数回手伝ったりしたのですが、彼が20冊ぐらいの日本語教科書の本文に出てくる単語をひたすらエクセルに入力した表を持っていて、それに品詞名を付けたいと言っていました。


f:id:midnightseminar:20151005222824p:plain


 こんな感じのCSV(といっても1列しかないのでカンマが出てこないですがw)で、単語一覧が縦に並んでいる。この各単語の横に、その品詞を挿入したいそうです。単語は延べ3万8000語ぐらいあるので、手作業は厳しい。
 というわけで、オープンソースの日本語形態素解析エンジンとして有名なMeCabと、MeCabをRから使うRMeCabパッケージを導入して、手伝いました。ついでにPythonバインディングもインストールしました。


 形態素解析ってのは、日本語の文章を単語(品詞)ごとに区切ってくれる技のことです。日本語テキストをコンピュータで処理する際に、たとえば単語の数を数えようと思っても、英語のように「分かち書き」されていないのでどこが区切りか分からない。だから、いったんテキストの全体に形態素解析をかけて、単語単位で区切ってやる必要があります。
 ちなみに、友人がやりたがっていたのは「すでに単語に分けられたデータに品詞を振る」だけの作業だったので、今回は分かち書きの処理自体は不要のはずだったのですが、実際には単語単位に分けきってない不完全なデータだったので、結局形態素解析は必要でした。


 Macの場合、MeCabはコマンドラインでインストールするので、慣れてないと色々不安な面もありますが、やってみたらすんなり行けました。以下、私なりのメモを書いておきますが、すごく参考になるようなものではないと思います。ターミナルからコマンドラインで操作するのがイマイチわからんという人には参考になるかもしれないです。
 なお、インストール後に行った友人の作業手伝いの内容については、次のエントリでまとめてあります。


 インストール方法の説明サイトはいくつかみましたが、Macの場合、


 MacにMecabをインストールする (2013.3) - Qiita


 このページを参考にするのが良いと思います。アンインストールのやりかたも書いてありますね。
 MeCabの本家サイトにも説明が色々載ってるんですが、WindowsとLINUXのインストール方法しか載っていません。
 MacもLINUXとほとんど同じなのでべつに良いとは言えるのですが、文字コードを指定するオプションとか付けないとダメっぽいです。
 ちなみに私のMacのOSはYosemiteです。

ダウンロード

 まずはMeCab本体とIPA辞書をダウンロードします。


 MeCab: Yet Another Part-of-Speech and Morphological Analyzer


 このページにいくと下の方に「ダウンロード」ってのがあるので、「MeCab本体 Source」をダウンロードします。
 「mecab-0.996.tar.gz」という名前の圧縮ファイルが手に入ります。
 次に、その下にある「IPA辞書」ってのをダウンロードします。
 「mecab-ipadic-2.7.0-20070801.tar.gz」という名前の圧縮ファイルが手に入ります。


 インストールに入る前に、ファイルの置き場をどうするか考えます。
 解説サイトを読むとだいたい、ダウンロードフォルダ(~/Downloads)内で圧縮ファイルを展開して、そこからインストールの作業をやってるのですが、ダウンロードしてきたデータ一式はアンインストールの時に必要だったりするし、私はダウンロードフォルダはなるべく常に空にしておきたいので、保存場所を決めることにしました。
 Dropboxと同期しているフォルダ内にR用のフォルダを設けてあるので、その中にMeCab用のフォルダをつくって、そこ(~/Dropbox/R/MeCab)に圧縮ファイルを移動させてからスタートすることにしました。


 なお、ダウンロード自体もターミナルからやるのであれば、ターミナルを立ち上げて

$ cd ~/Dropbox/R/MeCab/   # 保存したいディレクトリに移動
$ curl -O https://mecab.googlecode.com/files/mecab-0.996.tar.gz
$ curl -O https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz


 とすればいいですね。
 コマンドラインをふだん使わない人向けにいうと、ターミナルで何か操作するときは1行のなかでまず冒頭でコマンドを書き、次にスペースをあけて「-」とか「--」で始まるオプション(設定みたいな意味)を必要があれば書き、その後に処理対象とかを具体的に記載します。
 上記のコマンドの「cd」ってのはchange directoryの略で、直後で指定するディレクトリへ移動するという意味。「~」はホームディレクトリといって、通常は自分のユーザ名が付いたディレクトリが設定されていますので、「~/Dropbox」と書くと、ホームディレクトリの中にある「Dropbox」ディレクトリに移動するという意味になります。
 curlというのはダウンロードするときにつかうコマンドで、「-O」というオプションを付けた上で、URLを直後に書くと、ファイルがダウンロードされます。

 以下、ターミナルでのコマンドは頭に「$」が付いてますが、これはターミナルを開いてみれば意味わかります。


f:id:midnightseminar:20151006081916p:plain

MBA2012:~ yk$ cd ~/Desktop

 ってなってますが、MBA2012:というのはコンピュータの名前を示し、その後のykってのはログインしてるユーザ名を指します。その後の$から後が自分が打ったコマンドです。以下の記述では、だいたい$以降を表示してます。
 「#」以降はコメントアウトです。

インストール

 あとは、解説サイトに従って、コマンドラインでインストールを行います。
 インストールの大まかな流れは、

  1. ダウンロードしてきたtarファイル(アーカイブ)をtarコマンドで展開する。
  2. 展開後のフォルダに移動する。
  3. ./configureコマンドでコンパイル環境の設定を行う。
  4. makeコマンドでコンパイルする。
  5. make checkコマンドでコンパイルエラーのチェックを行う。
  6. make installコマンドでインストールを行う。


 という感じで、これをMeCab本体とIPA辞書のそれぞれに対して実施します。


 まずはMeCab本体のインストールを進めていきます。
 以下のコマンドを順番に打っていきます。(「~/Dropbox/R/MeCab/」という場所に先ほどダウンロードしたファイルが保存されていることが前提です。)

$ cd ~/Dropbox/R/MeCab/        # MeCabフォルダに移動
$ tar zxfv mecab-0.996.tar.gz  # tarアーカイブを展開
$ cd mecab-0.996               # 展開されて出てきたフォルダに移動
$ ./configure                  # コンパイル環境の自動設定を行う
$ make                         # コンパイル


 これで、通常であればコンパイルが行われます。
 なお、./configureと打った後などは、処理が始まってターミナル上に大量の文字列が勝手に流れてくるので、止まるまで放置してればいいです。

参考:Xcodeライセンスへの同意

 さて、ここまできて、あくまで私の場合にたまたま起きた出来事なのですが、

Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.


 というメッセージが出ました。XCodeのライセンスへの同意が必要みたいなことを言ってますね。
 これは発生条件がよく分かりませんが、ググってみると、OSのアプデとかをした後に求められるものらしいです。
 osx mavericks - "sudo xcodebuild -license" fails to cure recurring xcode license prompt - Stack Overflow


 sudoで打ち直すと、

MBA2012:mecab-0.996 yk$ sudo make
Password:

You have not agreed to the Xcode license agreements. You must agree to both license agreements below in order to use Xcode.
Hit the Enter key to view the license agreements at '/Applications/Xcode.app/Contents/Resources/English.lproj/License.rtf'


 と出て、やはりXcodeのライセンスに同意しろということのようです。
 returnキーを押すとライセンスに関する記述(長い英文)が表示されます。


f:id:midnightseminar:20151005221103p:plain


 これを下へ送って行くと最後に、

By typing 'agree' you are agreeing to the terms of the software license agreements. Type 'print' to print them or anything else to cancel, [agree, print, cancel]


 と表示されたので、「agree」と打ったら終わりました。


 ところが、その後「make」が続いていくのかと思ったら、「makefileがありません」的なメッセージが表示されて何もできなくなりました。そこで一旦ターミナルを終了して、./configureからやり直したら、コンパイルが無事始まりました。


 このXcodeライセンスの件は、たまたま私の場合このタイミングで起きたものであり、MeCabとは直接関係ないのですが、せっかくなのでメモっときました。

インストール

 さて、コンパイルが完了したら、

$ make check   # チェック(テストが実施される)


 と打って、コンパイル結果のチェックを走らせます。


f:id:midnightseminar:20151005221155p:plain


 ↑のように、テスト完了という表示が出たら成功ですね。何もエラーが出ていなかったので、インストールに進みます。

$ sudo make install   # インストールの実施


 sudoを頭につけるとsuper user、つまり特権的な権限で何かを実行するという意味になり、Macへのログインやアプリインストールのときなどに使っているパスワードの入力を求められます(Apple IDのパスワードとは別ですよ)。ターミナル上でパスワードを入力してreturn(enter)を押せばOKです。
 ここでも、文字列が大量に流れてくるので止まるまで放置し、何もエラーを出さずに停止したら、インストール完了です。

辞書のインストール

 次は形態素解析に使われるIPA辞書のインストールです。

$ cd ~/Dropbox/R/MeCab/                        # MeCabフォルダに移動
$ tar zxfv mecab-ipadic-2.7.0-20070801.tar.gz  # tarアーカイブを展開
$ cd mecab-ipadic-2.7.0-20070801               # 展開して出てきたフォルダに移動
$ ./configure --with-charset=utf8              # 文字コードがUTF-8になるオプションをつける
$ make                                         # コンパイル
$ sudo make install                            # インストール


 デフォルトでは文字コードがEUCになるらしいので、./configureのオプションとしてUTF-8指定は必要ですね。

形態素解析を実行

 さて、これでインストールは完了しているはずです。
 ターミナル上で「mecab」と打ってreturnを押し、その後に文字列を入れると、解析が実行されます。
 たとえば、他の解説サイトにならって、

$ mecab
すもももももももものうち


 と入力してみます。すると・・・

MBA2012:mecab-ipadic-2.7.0-20070801 yk$ mecab
すもももももももものうち
すもも    名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も    助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
も    助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
の    助詞,連体化,*,*,*,*,の,ノ,ノ
うち    名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS


 おおおお、成功しました!
 MeCabで実行できるその他の処理や辞書のカスタマイズなどは、ググると色々出てきます。


 なお、こんなややこしいことしなくても、Homebrew等のパッケージ管理ソフトからインストールできるようです。
 MacにMacPortsを使ってmecabを入れる | joppot
 Python で Mecabを利用する【mac】 - 39Si
 どこかに、MacPortsでのインストールは非推奨と書いてあったような気がしますが。

RMeCabのインストール

 さて次は、MeCabをRから使えるようにするRMeCabパッケージをRに導入します。これにより、日本語テキストに対して統計解析を行えるようになるというわけです。
 インストールはめちゃめちゃ簡単で、CRANパッケージではないんですが、
 http://continue-is-power.com
 ここに書いてあるように、Rを起動して、コンソール上で、

install.packages("RMeCab", repos = "http://rmecab.jp/R")


 というコマンド一発打ったら終わりです。
 これ以降は、Rから、

> library(RMeCab)
> RMeCabC("すもももももももものうち")
[[1]]
    名詞
"すもも"

[[2]]
助詞
"も"

[[3]]
  名詞
"もも"

[[4]]
助詞
"も"

[[5]]
  名詞
"もも"

[[6]]
助詞
"の"

[[7]]
  名詞
"うち"


 こんな感じで形態素解析が実行できます。
 RMeCabの使い方はいろんなところに解説がありますね。
 RパッケージRMeCabで用意されている関数

Pythonからも動かせるように

 次に、PythonからMeCabを動かすやつを入れてみます。

$ cd ~/Dropbox/Python/MeCab/   # 移動しておく
$ wget https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz   # ダウンロード
$ pip install mecab-python-0.996.tar.gz   # インストール


 これで一瞬で終了しました。
 ダウンロードはwgetではなく

curl -O https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz

 でもいいです。(Macは標準ではwgetコマンドがはいってないし。)


 使い方は、
 https://taku910.github.io/mecab/bindings.html
 ここに載ってますが、

>>> import MeCab
>>> m = MeCab.Tagger ("-Ochasen")
>>> print m.parse ("すもももももももものうち")
すもも    スモモ    すもも    名詞-一般
も    モ    も    助詞-係助詞
もも    モモ    もも    名詞-一般
も    モ    も    助詞-係助詞
もも    モモ    もも    名詞-一般
の    ノ    の    助詞-連体化
うち    ウチ    うち    名詞-非自立-副詞可能
EOS


 できました。

MeCabのアンインストール

 最初の解説サイトに載ってた内容そのまんまですが、MeCabをアンインストールするには、アーカイブを展開したフォルダに移動してから「make uninstall」すればいいだけです。

#  辞書をアンインストール
$ cd ~/Dropbox/R/MeCab/mecab-ipadic-2.7.0-20070801
$ sudo make uninstall

# MeCabをアンインストール
$ cd ~/Dropbox/R/MeCab/mecab-0.996
$ sudo make uninstall

レコード・CD・有料ダウンロード等の販売量推移のグラフ

 音楽コンテンツの媒体別販売量の推移が気になって昔グラフにしてたのを久しぶりにみつけたので、2014年まで数字を入れて更新しておいた。


 元データはここです。
 一般社団法人 日本レコード協会|各種統計
 有料ダウンロード件数については「有料音楽配信売上実績」ってとこをクリックすると見れます。
 
 

グラフ

 媒体ごとに合算してグラフにしたのが下図です。


 f:id:midnightseminar:20150823020143p:plain
 
 
 有料ダウンロードの内訳をみると、下図のとおり、ガラケー時代に着うた・待ちうたが市場を支えていたのが消滅していったので、じつは萎んでいるようです。(着うた・待ちうた分がいくらなのかのデータは2006-2012しかない。)


 f:id:midnightseminar:20150823021116p:plain


 これも日本のガラパゴス的市場の特色なんでしょうかね。電子書籍に関して、ガラケー向けのエロ漫画とか(別にエロに限らないとは思うけど)の配信がかなりあったため日本の電子書籍市場は歴史的にかなりデカかったという話を思い出しました。
 
 

感想等

  • スチールカメラ→デジタルカメラなどと同じで、技術が急速に代替されていく感じが分かってよい。
  • CDは技術的には70年代から存在したが本格的に普及したのは80年代半ば以降である。
  • CDが普及すると、レコードは5年ぐらいで一気に駆逐されてしまった。
    • この時期にレコード関係の機器製造とかに携わっていた人はどんな気持ちだったんだろうか。
  • カセットテープの販売は案外多かった。そういえば俺が子供の頃、子供向けの戦隊物やアニメの主題歌や、NHKなどの子供向け番組の音楽が、カセットテープでよく売られていたのを覚えている。
  • 着うた・待ちうたのデータが2006年から2012年までしかないため、全体のグラフと一緒にするとややこしいのでしてないのだが、着うた・待ちうたを除くと有料DLの件数はあまり大きくは変わってないようだ。
  • 着うた・待ちうたを除くと、CDと有料DLを合算しても90年代後半のCD売上ピークには及ばず、緩やかに音楽コンテンツ市場そのものが縮小していっているような感じだ(1997年は総計4.8億枚→2012年は総計3.7億枚・回)。

 
 

注釈

  • 有料DL以外は「生産数量」で、有料DLは販売量に当たるので、並べていいのかは不明ですが、まぁそんなに気にしなくていいということにする。
  • 上のグラフは主に「レコードってどのぐらいの勢いで消えていったんだっけ?」ってのが気になって作成したものだったので、音楽ビデオの数量は除いている。
    • ただし有料DLについては2005年は内訳として音楽ビデオの数字がなかったので除いておらず、2006年は「その他 (音楽ビデオ等)」ってのを除いている。
  • 1950年からしかグラフにしてないが元ネタの数字は1929年から掲載されていた。