matplotlibをオブジェクト指向スタイルで使う その2

前にmatplotlibをオブジェクト指向スタイルで使う - minus9d's diaryという記事を書きました。しかし、matplotlib によるデータ可視化の方法 (1) - Qiita および Why do many examples use "fig, ax = plt.subplots()" in Matplotlib/pyplot/python - Stack Overflow によると、plt.subplots()という関数を使うと、より簡潔に書けるようです。この記事ではplt.subplots()の例を紹介します。

なお、以下のコード片では、全て冒頭に

import matplotlib.pyplot as plt
import numpy as np

と書かれているものとします。

一つの図に一つのグラフの場合

まず、一つの図と一つのグラフ領域を持つ絵を、私が前の記事で紹介した方法で描くと以下のようになります。

x = np.arange(0, 4, 0.1)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, x**2)

今回紹介するplt.subplots()を使うと、以下のように一行減り簡潔に書けます。

x = np.arange(0, 4, 0.1)
fig, ax = plt.subplots()
ax.plot(x, x**2)

参考までに、plt.show()して表示されるグラフを示します。 f:id:minus9d:20160421213310p:plain

一つの図に複数のグラフの場合

subplots()に引数を入れると、複数のグラフ領域を持つ絵もかけます。例えば

x = np.arange(0, 4, 0.1)
fig, axes = plt.subplots(2, 2)
for j in range(2):
    for i in range(2):
        ax = axes[j, i]
        ax.plot(x, x**(j*2+i))

とすると、以下のような絵がかけます。

f:id:minus9d:20160420230454p:plain

同じことを行う以下のコードと比較してみてください。

x = np.arange(0, 4, 0.1)

fig1 = plt.figure(1)
ax1 = fig1.add_subplot(221)
ax1.plot(x, x**0)

ax2 = fig1.add_subplot(222)
ax2.plot(x, x**1)

ax3 = fig1.add_subplot(223)
ax3.plot(x, x**2)

ax4 = fig1.add_subplot(224)
ax4.plot(x, x**3)

(2016/4/21追記)ブコメでもっとよい書き方を教えていただきました、ありがとうございます。add_subplot()に3つの引数を与えることでループ化が可能だそうです。

x = np.arange(0, 4, 0.1)
fig1 = plt.figure(1)
for i in range(4):
    ax = fig1.add_subplot(2,2,i+1)
    ax.plot(x, x**i)

もっともこのようにループ化した場合であっても、figureの作成処理とaxの作成処理の2回が必要であり、subplots()を使う場合より繁雑なところは変わりません。

pythonでcsvを読む方法 - 標準ライブラリ, pandas, numpy

pythoncsvを読み込む方法についてまとめました。ライブラリによって微妙に読み込み方が異なるので大変です。

この記事では、以下のdata.csvを読み込む場合を考えます。最初の行がヘッダ行で、それ以降の行がデータ行です。

a,b,c
2,5.6,1
1,7.0,0
3,6.2,1
3,7.9,1

方法1: 標準ライブラリのcsvを使う方法

csv.readerオブジェクトを使って一行ずつ読んでいく方法です。ヘッダ行の部分を特別扱いする必要があります。

import csv
def open_with_python_csv(filename):
    data = []
    with open(filename, 'r') as filename:
        reader = csv.reader(filename)
        # ヘッダ行は特別扱い
        header = next(reader)
        # 中身
        for row in reader:
            data.append(row)
    return header, data

header, data = open_with_python_csv(csvfile)

header, dataをprintすると以下のようになります。

header: ['a', 'b', 'c']
data: [['2', '5.6', '1'], ['1', '7.0', '0'], ['3', '6.2', '1'], ['3', '7.9', '1']]

文字列ではなく数字が必要な場合は別途手動で変換が必要です。

高機能とはいえませんが、標準ライブラリのためどの環境でも使えるのが強みと言えます。

方法2: pandasを使う方法

データ分析ツールのpandasを使うこともできます。

import pandas

def open_with_pandas(filename):
    df = pandas.read_csv(filename)
    header = df.columns.values.tolist()
    data = df.values
    return df, header, data

df, header, data = open_with_pandas(csvfile)

header, dataをprintすると以下のようになります。

header: ['a', 'b', 'c']
data: [[ 2.   5.6  1. ]
 [ 1.   7.   0. ]
 [ 3.   6.2  1. ]
 [ 3.   7.9  1. ]]

header, dataの型はそれぞれlist, numpy.ndarrayです。 数値が自動的に適当な型で保持されています。数値の型を指定することもできますが本記事の内容を超えます。 pandasを使ってデータの処理を行いたいときにはよい選択です。

方法3: numpyを使う方法

numpyにも、csvの読み込みに使える関数がloadtxt()とgenfromtxt()の2つ存在します。

numpy.loadtxt()を使う場合

もしヘッダ行が不要な場合は、以下のようにskiprowsを設定すればOKです。デリミタを明示的に指定する必要があるのに注意が必要です。

import numpy as np
def open_with_numpy_loadtxt(filename):
    data = np.loadtxt(filename, delimiter=',', skiprows=1)
    return data

data = open_with_numpy_loadtxt(csvfile)

dataの型はnumpy.ndarrayです。

ヘッダ行も読み込みたい場合は、以下の2通りの方法があるようです。まず一つ目。

import numpy as np
def open_with_numpy_loadtxt_2(filename):
    with open(filename, 'r') as file:
        line = file.readline()
        header = line.strip().split(',')
        data = np.loadtxt(file, delimiter=',')
    return header, data

header, data = open_with_numpy_loadtxt_2(csvfile)

そして二つ目。

import numpy as np
def open_with_numpy_loadtxt_3(filename):
    with open(filename, 'r') as file:
        lines = list(file)
    header = lines[0].strip().split(',')
    data = np.loadtxt(lines[1:], delimiter=',')
    return header, data

header, data = open_with_numpy_loadtxt_3(csvfile)

numpy.genfromtxt()を使う場合

genfromtxt()はloadtxt()に比べて、欠損値を埋める機能などがあり高機能だそうです。

まずはヘッダ行を読み飛ばす方法です。デリミタの指定が必要なのはloadtxt()と同じです。

def open_with_numpy_genfromtxt(filename):
    data = np.genfromtxt(filename, delimiter=',', skip_header=1)
    return data```

data = open_with_numpy_genfromtxt(csvfile)

ヘッダ行を解析する方法がまだ分かっていません。genfromtxt()にnames=Trueという引数を加えると、コラムに名前が付いたnumpy.ndarrayが得られるのですが、二次元配列ではなくなってしまいます。

import numpy as np
def open_with_numpy_genfromtxt_2(filename):
    data = np.genfromtxt(filename, delimiter=',', names=True)
    header = data.dtype.names
    return header, data

header, data = open_with_numpy_genfromtxt_2(csvfile)
# data[0]['a']などとアクセス可能

全コード

最後に、この記事を書くのに使ったスクリプトを貼っておきます。

read a csv file using python

Emacsに入ってる人工無脳ELIZAと対話する

AlphaGo | Google DeepMindと人類の戦いが最高潮なのにかこつけて、人工知能ネタを紹介します。

EmacsELIZA(イライザ)と呼ばれる、簡素な対話プログラムが入っているのはご存知でしょうか? ELIZAはJoseph Weizenbaumによって1964から1966年に書かれたプログラムで、人間の入力した言葉に対して、サイコセラピストであるかのような返答を返すというプログラムです。Emacs版の実装を試すには、M-x doctorとします。すると

I am the psychotherapist.  Please, describe your problems.  Each time
you are finished talking, type RET twice.

と、問診が始まります。「頭痛がする」と返してみましょう。

(私) I have a headache.

(ELIZA) Maybe you should consult a medical doctor; I am a psychotherapist. .

医者にかかれと怒られてしまいました。では、今度は「気持ちが落ち込んでいる」と返してみます。

(私) I am depressed.

(ELIZA) Are you depressed often?

会話を続けてみます。

(私) Yes.

(ELIZA) I see...  Well, why do you say this is so?

(私) I have a lot of work.

(ELIZA) What makes you believe you have a lot of work?

この辺で早くも限界を感じますが、続けてみます。

(私) I have to overwork every day.

(ELIZA) Earlier you said you felt depressed?

(私) Yes.

(ELIZA) Yes, hmmm... so, are you sure that this is so?

(私) Yes.

(ELIZA) I understand.  So do you really think this is so?

(私) Yes.

(ELIZA) Oh. ...And what makes you think this is so?

堂々巡りになり埒が明きません。目先を変えて、「死にたい」と伝えてみます。

(私) I want to die

(ELIZA) This is not a healthy way of thinking.

さらに直球で自殺をほのめかすと

(私) I wanna commit suicide.

(ELIZA) If you are really suicidal, you might want to contact the Samaritans
via E-mail: jo@samaritans.org or, at your option, anonymous E-mail:
samaritans@anon.twwells.com .  Or find a Befrienders crisis center at
http://www.befrienders.org/ .  I would appreciate it if you would
continue.

こころの相談室的なところを紹介されてしまいました。急にマジレスされて驚きました。

ソースコードらしきものがEmacs Doctorにあります。これを見るとELIZAが反応しそうな単語が分かります。

最後に、さよならの挨拶をしてみましょう。

(私) Bye!

(ELIZA) My secretary will send you a bill.

2016年センター試験英語(筆記)を解く

英語力の定点観測のために、今年のセンター試験英語(筆記)を解いてみました。結果からいうと183点で、期待よりも低い値でした。

以下、間違えた部分を中心としたメモです。ネタバレしてます。

第1問

B-3. charity, continent, demonstrate, opponentでアクセントの場所が異なる単語を選ぶ問題を間違えた。op-po-nentは2つ目の音節にアクセントがあり、他の単語は1つ目の音節にアクセントがあるので、opponentを選ぶのが正解。私はdemonstrateを誤選択。

第2問

A-9. "Wood (used to) be used as the main fuel, but nowadays fossil fuels (are used) widely."という文を完成させる問題。誤って(was used to), (are used)という選択肢を選んだ。正解すべき問題だった。

A-10. "(It) is so considerate (of) him to..."という、人の評価を述べるときに使う構文を完成させる問題。こんな構文の存在を完全に忘れていた。

C-3. PaulとYokoが結婚してる…? Johnではなく…?

第3問

  1. 不要な文を除く問題。英語力というより論理力が問われる。B-2とB-3はどの文もはっきりと不要といえる文はないように思えた。B-3で、罪悪感の話題が他と繋がらないため4を選んだが正解は1。納得できていない。

第4問

  1. 美術館のウェブサイトから情報を読み解く問題。TOEICみたいだ。

第6問

A-5. パッセージにタイトルを付ける問題。"The Difficulties Facing Opera"と"The Historical Context of Opera"とで迷って後者を選択。正解は前者。最近のオペラの問題以外の歴史的な話題もあったのでより広いタイトルを選んだのだが…。

Anacondaのcondaコマンドによる仮想環境の使い方のまとめ

Pythonパッケージ集として人気のあるAnacondaに付属するcondaコマンドを使って、クリーンなPython環境を作ったり破棄したりする方法についてまとめました。

環境を作る

myenvという名前のpython環境を作ってみましょう。

$ conda create --name myenv python

とすると、myenvという名前の環境が作成されます。以下、--nameの代わりに-nでもOKです。

Pythonのバージョンを指定したい場合はpython=x.xなどと書きます。

$ conda create --name myenv python=3.3

追加のパッケージを入れたい場合はパッケージ名を羅列します。

$ conda create --name myenv numpy scipy

パッケージ全部入りのpython環境を作るには最後をanacondaにします。

$ conda create --name myenv anaconda

すでに作成した環境をクローンして新たな環境を作成したい場合は--cloneを使います。ここではmyenvという環境をクローンしてclonedenvという環境を作成しています。

$ conda create --name clonedenv --clone myenv

作成した環境の一覧を見る

以下のコマンドを打ちます。

$ conda info --envs

自分が今いる環境にはアスタリスクが付与されています。

環境を切り替える

Linux, OS Xの場合は

$ source activate myenv

Windowsの場合は

$ activate myenv

とすると、作成した環境に入れます。

環境から出る

Linux, OS Xの場合は

$ source deactivate

Windowsの場合は

$ deactivate

とすると、作成した環境から出られます。

環境を消す

$ conda remove --name myenv --all

とします。

参考

Managing environments — Conda documentation

VirtualBOX + Ubuntuの環境構築メモ

Windows 10 + VirtualBOX + Ubuntuを動かすときのメモです。適宜更新します。

Ubuntuの仮想イメージを取得

Ubuntuの入手 | Ubuntu Japanese Teamから「日本語 Remix 仮想ハードディスクイメージのダウンロード 」をクリックして「Ubuntu 14.04 LTS」を入手します。

LTSはLong Term Supportの略。サポート期間が長いので、LTSのついたものを選ぶのがよさそうです。

もっさりの解消

Ubuntuを起動すると、Unityのあまりの遅さに驚きます。VirtualBox 上の Ubuntu がもっさりする件を解消するメモ - Qiitaに従って3D アクセラレーションを有効化すると、もっさりが解消しました。

よく使うソフトのインストール

まずパッケージを更新

$ sudo apt-get update
$ sudo apt-get upgrade

個人的によく使うソフトをインストール

$ sudo apt-get install emacs git lv nkf python-pip zsh

C言語でOpaqueポインタを使って構造体のメンバを隠蔽する

C言語で、自作の構造体のメンバをユーザに開示しないテクニックとして、Opaqueポインタというものが知られています。今回は、書籍「C++のためのAPIデザイン」の3.1.6節を参考に、Opaqueポインタを使う簡単なサンプルを紹介します。

Opaqueポインタを使わない場合

人に関するデータを集めた構造体Personと、その構造体を使ったライブラリを作成することを考えます。以下にPerson.hのコードを示します。

#pragma once

typedef struct _Person
{
    int age;
} Person;

// 以下、Person構造体に関するAPI

Person* createPerson(int age);    // Person構造体のオブジェクトを生成
void printPerson(Person* ptr);    // Person構造体を使った操作(メンバのプリント)
void destroyPerson(Person* ptr);  // Person構造体のオブジェクトを破棄

Person構造体のメンバがベタ書きされていることに注目してください。

Person.cppのコードを以下に示します。

#include "Person.h"
#include <stdio.h>

Person* createPerson(int age)
{
    Person* ptr = (Person*)malloc(sizeof(Person));
    if (ptr)
    {
        ptr->age = age;
    }
    return ptr;
}

void printPerson(Person* ptr)
{
    if (ptr) printf("age: %d\n", ptr->age);
}

void destroyPerson(Person* ptr)
{
    if (ptr) free(ptr);
}

と記述します。

このPerson構造体の呼び出し側のコードは以下のとおりです。

#include "Person.h"

int main(void)
{
    Person *p = createPerson(42); // オブジェクトを生成
    printPerson(p);
    destroyPerson(p); // オブジェクトを破棄

    return 0;
}

createPerson()を使ってPerson構造体のオブジェクトを新規作成し、Person構造体を利用する関数を呼び出した後、destroyPerson()を使ってオブジェクトを破棄しています。

この例ではPerson.hにPerson構造体のメンバをすべてベタ書きしているため、以下のような問題が生じます。

Personオブジェクトの中身を、呼び出し側が自由に変更できてしまう。 上記例では、呼び出し側がp->age = 100;などと内部の値を自由に変更できてしまいます。これはライブラリの作者が予期しない結果をもたらす可能性があります。

Person構造体の実装が外部に露出してしまう。 Person構造体の実装は呼び出し側が知る必要がない情報であっても、実装が露出してしまっています。

Person構造体に変化があるたびに、Person.hの呼び出し側で再コンパイル・再リンクが必要。 ビルドの時間が増加します。またライブラリ側と呼び出し側で異なる定義のPerson構造体を想定している場合、リンクすると危険なことが起こるはずです。

Opaqueポインタを使う場合

上述の問題を解決するためにOpaqueポインタが使われます。修正したPerson.hを以下に示します。

#pragma once

typedef struct Person *PersonPtr;

PersonPtr createPerson(int age);
void printPerson(PersonPtr ptr);
void destroyPerson(PersonPtr ptr);

上記では、Person構造体の定義はしていません。すなわち、Person構造体のメンバを具体的に書き下すことはしていません。そのかわりに、どこかに定義されているPerson構造体へのポインタを、PersonPtrという型名で呼ぶという約束だけをしています。こうすることでPerson.hの呼び出し側にPerson構造体の実装が漏れることがなくなりました。

修正したPerson.cppは以下です。

#include "Person.h"
#include <stdio.h>
#include <stdlib.h>

struct Person
{
    int age;
};

PersonPtr createPerson(int age)
{
    PersonPtr ptr = (PersonPtr) malloc(sizeof(struct Person));
    if (ptr)
    {
        ptr->age = age;
    }
    return ptr;
}

void printPerson(PersonPtr ptr)
{
    if (ptr) printf("age: %d\n", ptr->age);
}

void destroyPerson(PersonPtr ptr)
{
    if (ptr) free(ptr);
}

呼び出し側は以下です。

#include "Person.h"

int main(void)
{
    PersonPtr p = createPerson(42);
    printPerson(p);
    destroyPerson(p);

    return 0;
}

構造体の中身は呼び出し側からは知る由はないので、もし仮にここでp->age = 70;などと構造体の中身を書き換えようとしても、コンパイルが通りません。Opaqueポインタを使うことで実装の詳細を適切に隠蔽でき、安全性も向上していることがわかります。