2013/12/29

Python の基本的な高階関数( map() filter() reduce() )

Python の高階関数のうち、組み込みで用意されている基本的なものについてご紹介します。

具体的には
  • map()
  • filter()
  • reduce()
の3つを見ていきます。

個別の説明に入る前に、まずは「高階関数」について少し説明を。

高階関数とは、英語の「 higher-order function 」の訳で、「他の関数を引数として受け取る関数」のことです。たとえば、リストの各要素に共通の処理を加えたリストを作成したい場合なんかに、処理をわかりやすく簡潔に記述することができます。

高階関数の仕組みを用意している言語は他にもたくさんありますが、 Python の場合は「関数はオブジェクトであり、関数に () をつけると初めて関数が呼び出される」決まりとなっています。 () なしで関数の名前だけを指定すればその関数そのものを捉えることができます。

たとえば sin 関数の場合。

print sin(10)  # => sin(10)の計算結果

print sin  # => sin関数を表すオブジェクト

my_sin = sin
print my_sin(10)  # => sin(10)の計算結果

というようなことになります。

では、 map() filter() reduce() の 3 つについて順に見ていきましょう。

map()


map() はリストやタプルの各要素に指定した関数を適用し、各適用結果を順番に返すイテレータを返す関数です。 map(func, iterator) の形で使います。

a = [-1, 3, -5, 7, -9]

print list(map(abs, a))
# => [1, 3, 5, 7, 9]

これは各要素の絶対値を計算する処理をしています。

ここで、 abs() は引数をひとつ取り、その絶対値を返す関数です。 list はイテレータを受け取ってリストとして返す関数(リストのコンストラクタ)です。 list() については、ここでは中身を表示するために使っているだけなので map() の使い方に直接は関係ありません。

map() の戻り値は、リスト a の各要素が abs() に渡された結果の新たなリストとなります( Python3 の場合はイテレータとなります)。

ちなみに、同様の処理をジェネレータ式で書くと次のようになります。

(abs(x) for x in a)  # => map(abs, a) と同じ結果

filter()

つづいて filter() です。 filter() はリストやタプルの要素のうち関数を適用した結果が True となるものだけをイテレータとして返す関数です。
a = [-1, 3, -5, 7, -9]

print list(filter(lambda x: abs(x) > 5, a))
# => [7, -9]
ここでは、無名関数 lambda を使いました。ここで lambda は「絶対値が 5 よりも大きいなら True 」を返す関数です。

filter() の結果として a の要素のうち絶対値が 5 よりも大きいものだけがリストで返ってきています。

ちなみに、こちらもタプル内包表記で書くと次のようになります。
(x for x in a if abs(x) > 5)  # => 上記filterと同じ結果

reduce()

最後に reduce() を見てみます。 reduce() はリストやタプルの要素を足しあわせたりかけあわせたりする関数です。「畳み込み」演算と呼ばれたりします。英語でいうと「 folding 」「 convolution 」でしょうか。

from operator import add
# from functools import reduce  # python 3 では必要

a = [-1, 3, -5, 7, -9]

print reduce(add, a)
# => -5

これはすべての要素を足し合わせるサメーションの計算をしています。

ここで、関数 add は演算子の「 + 」と同じ処理を行う関数です。 Python では演算子である + を reduce に渡すようなことはできないため、 + のかわりに add を使っています。

reduce() の結果として a の要素の総和である -5 が返ってきています。

もちろん、 lambda を使っても同様の処理を行うことが可能です。
print reduce(lambda x, y: x + y, a)

ちなみに、畳み込み演算に対応した内包表記はありません。

追記: Python 3 では reduce() は標準ライブラリ functools に移動されたので、利用するには import してくる必要があります。


おまけ: apply()

Python の 2.3 あたりまでは apply() という関数もあったそうです。しかし、 apply でできる処理は「 * 演算子」(リストなどを展開するスプラット演算子)を使った引数展開によって可能なので(可能となったので?)、推奨されていないようです。

ちなみに、 * 演算子での引数展開は次のように行います。

from operator import add  # addは引数を2つ取る関数

a = [1, 2]

print add(*a)  # => 3
# add は引数を2つ取るインタフェースとなっているため
# リストaを直接受け取ることはできない
# add(*a) とすると a の中身が展開され
# add(1, 2) と同じ動きをする

以上です。


余談ですが、四則演算のための演算子( + - * / )や比較演算子( < <= == >= > )を高階関数に渡したいときは、 operator ライブラリを使うと便利です。演算子と同じ働きをする関数がまとめられています。


参考
高階関数について
高階関数とは - はてなキーワード
高階関数 - Wikipedia
たのしい高階関数

Pythonの高階関数について
2. Built-in Functions — Python公式ドキュメント
Python の map, filter, reduce とリスト内包表記 | すぐに忘れる脳みそのためのメモ
Understanding the Map function. python - Stack Overflow
list - Filters in Python - Stack Overflow

operatorライブラリについて
9.9. operator — Standard operators as functions — Python v2.7.6 documentation

2013/12/23

Python Tips:改行なしで文字列を出力したい

Python の print は、末尾に改行を追加して出力するのがデフォルトの動作となっています。これを改行なしで出力する方法をご紹介します。

方法は2つあります。
  1. print string,
  2. sys.stdout.write(string)

以下、順に見ていきます。


print string,

ひとつめは print string, を使う方法です。
print "hello",
print "hello",
print "hello"
# => hello hello hello

print 文の末尾に「,」をつけると、改行が半角スペースに置き換えられます。これは、いくつもの変数をまとめて表示する際に使える「,」を利用した方法です。
print "hami", "gaki", "ko"
# => hami gaki ko


sys.stdout.write(string)

もうひとつは sys.stdout.write() を使う方法です。
import sys
sys.stdout.write("hello")
sys.stdout.write("hello")
sys.stdout.write("hello")
# => hellohellohello
こちらの方法だと半角スペースが挿入されることもなく、渡した文字列がただそのまま表示される形となります。空白もコントロールしたい場合はこちらを使うのがよろしいかと思います。

ただし、 sys.stdout.write()print 文と異なり、文字列以外を受け取ることができません。文字列になっていないものは str などを使ってフォーマットした上で渡すようにしましょう。

以上です。


上記の方法はあくまでも Python2 でのやり方です。 Python3 の場合は、 print が「 print 文」から「 print 関数」へと変わり、それに合わせて、末尾に追加される文字を end オプションで指定できるようになりました。

Python3 で改行を入れてほしくない場合は次のようにするとよいようです。
# case in Python 3
print("hello", end="")
print("hello", end="")
# => hellohello

ちなみに、 Python2.6 以降であれば、次の一文を宣言すると、 print 文を上書きする形で Python3 の print 関数を使うことができるようになります。
# case in Python 2.6~
from __future__ import print_function
print("hello", end="")
print("hello", end="")
# => hellohello

print に興味のある方はこちらの記事も参考になるかもしれません。

Python 3 の print() 関数の使い方
Python 2 の print 文の使い方


参考
Input and Output - 公式ドキュメント Python Tutorial
print function - Python公式ドキュメント

2013/12/13

ライブラリ:pygooglechart

Pythonの「 pygooglechart 」というライブラリについてご紹介します。

from pygooglechart import *

Gooleが提供しているサービスのひとつに「Google Chart」というものがあります。HTTPリクエストでグラフを生成できるというすてきサービスなのですが、そのGoogle ChartのPython用ラッパーがこの pygooglechart です。

pygooglechart を使えば、ローカル環境にプロット用のツールが入っていなくても、インターネット環境さえあればほんのちょっとのコードでグラフを生成することができます。

以下、使い方をざっと見ていきます。
  • 基本的な使い方
  • 折れ線グラフ
  • 棒グラフ
  • 散布図
  • 円グラフ
  • QRコード
  • 基本的なメソッド

基本的な使い方

まずはもっとも基本的な使い方から。大きな流れは次のとおりとなります。
  • ライブラリの読み込み
  • インスタンスの生成
  • プロットデータの追加
  • 結果の取得

# ライブラリの読み込み
from pygooglechart import Chart
from pygooglechart import SimpleLineChart
from pygooglechart import Axis

# チャートインスタンスの生成
Width = 400
Height = 250
chart = SimpleLineChart(Width, Height)

# プロットデータの追加
chart.add_data([1, 5, 3, 8, 11])
chart.add_data([9, 2, 4, 8, 13])

# 結果の取得
print chart.get_url()  # グラフ画像取得用のURLを表示
chart.download("basic.png")  # 実際に生成されたグラフ画像のダウンロード

上記スクリプトを実行すると、ターミナルにURLが表示され、 basic.png が実行ディレクトリに生成されます。


基本のこの型さえ使えるようになれば、あとは線種やタイトル、凡例などを設定するオプションを都度覚えるだけです。べんり!

以下、さまざまなタイプのグラフを見ていきます。


折れ線グラフ

折れ線グラフの生成には SimpleLineChart というクラスを使用します。

# ライブラリの読み込み
from pygooglechart import Chart
from pygooglechart import SimpleLineChart
from pygooglechart import Axis

# インスタンスの生成
Width = 400
Height = 250
chart = SimpleLineChart(Width, Height)

# プロットデータの追加
chart.add_data([1, 5, 3, 8, 11])
chart.add_data([9, 2, 4, 8, 13])

# 各種オプションの設定
# 折れ線の色の設定
# 16進数表記で与える
chart.set_colours(['333333'])

# 線種を設定
# index は系列の番号 thickness は太さ
chart.set_line_style(index=0, thickness=5)
chart.set_line_style(index=1, thickness=10)

# X軸/Y軸ラベルの追加
# 第1引数に位置を、第2引数にラベルを渡す
# 渡したリストの各要素が自動的に均等配置される
chart.set_axis_labels(Axis.BOTTOM, [0, 1, 2, 3])
chart.set_axis_labels(Axis.LEFT, ["low", "middle", "high"])

# グラフのタイトルの追加
chart.set_title("line chart")

# 結果の取得
chart.download("line.png")



棒グラフ

棒グラフの生成には GroupedVerticalBarChart というクラスを使用します。

# ライブラリの読み込み
from pygooglechart import GroupedVerticalBarChart

# インスタンスの生成
Width = 400
Height = 250
chart = GroupedVerticalBarChart(Width, Height)

# プロットデータの追加
chart.add_data([1, 2, 3, 4, 5, 6])
chart.add_data([1, 25, 9, 16, 36, 4])

# 各種オプションの設定
# 系列ごとの色の設定
chart.set_colours(['333333', 'cc3333'])

# 系列ごとの凡例の追加
chart.set_legend(['x', 'y'])

# 結果の取得
chart.download('bar.png')


ちなみに、折れ線グラフ用のクラスには GroupedVerticalBarChart のほかにも次のようなクラスが用意されています。

  • StackedHorizontalBarChart
  • StackedVerticalBarChart
  • GroupedHorizontalBarChart
  • GroupedVerticalBarChart


棒グラフの伸びる向きや複数の系列をどう表すか、というところがクラスごとに異なる点です。詳細は公式サイトをご覧ください。


散布図

散布図の生成には ScatterChart というクラスを使用します。

# ライブラリの読み込み
from pygooglechart import Chart
from pygooglechart import ScatterChart
from pygooglechart import Axis
import random

# インスタンスの生成
# x_range y_range はX軸/Y軸の範囲を設定するためのオプション
Width = 400
Height = 250
chart = ScatterChart(Width, Height,
                     x_range=(0, 100), y_range=(0, 100))

# プロットデータの追加
# 最初のデータはX軸上の位置 2番目のデータはY軸上の位置 3番目のデータは各点のサイズを表す
NUM_OF_POINTS = 30
chart.add_data([random.randrange(100) for _ in range(NUM_OF_POINTS)])
chart.add_data([random.randrange(100) for _ in range(NUM_OF_POINTS)])
chart.add_data([random.randrange(1, 30) for _ in range(NUM_OF_POINTS)])

# 各種オプションの設定
# ドットの色の設定
chart.set_colours(['333333'])

# X軸/Y軸目盛りの追加
# 第1引数に軸を 第2引数に加減を 第3引数に上限を渡す
# 目盛り幅は自動で与えられる
chart.set_axis_range(Axis.BOTTOM, 0, 100)
chart.set_axis_range(Axis.LEFT, 0, 100)

# 結果の取得
chart.download('scatter.png')



円グラフ

円グラフの生成には、 PieChart2D というクラスを使用します。

# ライブラリの読み込み
from pygooglechart import PieChart2D

# インスタンスの生成
Width = 500
Height = 250
chart = PieChart2D(Width, Height)

# プロットデータの追加
chart.add_data([10, 10, 30, 50])

# オプションの設定
# 各データにラベルを追加
chart.set_pie_labels([
    'ハンバーガー',
    'チーズバーガー',
    'フィレオフィッシュ',
    'ポテト',
    ])

# 結果の取得
chart.download('pie.png')


ちなみに、3Dの円グラフを生成する PieChart3D というクラスも用意されています。


QRコード

QRコードの生成には、 QRChart というクラスを使用します。

# ライブラリの読み込み
from pygooglechart import QRChart

# インスタンスの生成
Width = 250
Height = 250
chart = QRChart(Width, Height)

# QRコードに埋め込むデータの追加
chart.add_data('http://www.google.co.jp')

# 結果の取得
chart.download('qr.png')



この他にもさまざまなグラフが用意されていますが
  • ライブラリを読み込んで
  • ***Chart というクラスのインスタンスを生成して
  • プロットデータを追加して
  • 各種オプションを設定して
  • 結果を取得する
という基本の流れは共通ですので、このあたりをひととおり身につけておけば、ちょっとしたときに使えて便利かと思います。

最後に、ここまでのおさらいも兼ねて使用頻度の高そうなメソッドを見てみます。


基本的なメソッド

add_data(データ系列を表すリスト)
プロットしたいデータを追加するためのメソッドです。データをリスト形式で渡します。

set_colours(16進数表記で色を表す文字列のリスト)
系列の色を設定するためのメソッドです。16進数表記で与えます。複数の系列がある場合は、リストで与えます。

set_title(文字列)
グラフタイトルを追加するためのメソッドです。

set_legend(文字列のリスト)
各系列を説明する凡例を追加するためのメソッドです。

set_axis_labels(Axis.BOTTOMなど, 文字列のリスト)
X軸やY軸にラベルを追加するためのメソッドです。多くの場合は、第1引数に Axis.BOTTOM か Axis.LEFT を、第2引数にラベルのリストを渡します。

set_axis_range(Axis.BOTTOMなど, 下限値, 上限値)
X軸やY軸に目盛りを追加するためのメソッドです。

x_range = (下限値, 上限値)
y_range = (下限値, 上限値)
X軸やY軸の表示範囲を決めるためのメソッドです。デフォルトではすべてのデータが含まれる範囲が自動的に設定されるので、あえてこれを変えたい場合に使います。

get_url()
結果として生成されたグラフ取得用のURLを返すメソッドです。

download(ファイル名)
get_url のURLに実際にアクセスして取得できるグラフ画像を保存するためのメソッドです。ファイル名を渡します。


以上です。


インストール

pip が入っていれば、pip install でのインストールが可能です。
$ pip install pygooglechart


GitHubのリポジトリにはいくつか動くサンプルが入っているので、感触をつかみたい場合には一度そちらで試してみることをおすすめします。


参考
Python Google Chart
gak/pygooglechart - GitHub
Google Charts — Google Developers

のーんびりと Webプログラム: Python Google Chartを使って大量に作成したQRコードをスライドショーで眺めると、QRコードの中に色々な形が見えてきます
Python Google Chart を使って日経225のグラフを描いてみる | 傀儡師の館.Python

2013/12/11

Python Tips:スクリプトとして実行されたときにだけ特定の処理を走らせたい

スクリプトとして実行されたときにのみ走らせたいコードの書き方についてご紹介します。

Pythonのコードを書いていると、そのコードが他からimportされたときには走らせたくないけれど、pythonコマンドで直接実行されたときには走らせたい、という場合があるかと思います。

そのような場合は __name__ 変数を使います。

...

if __name__ == "__main__":
    # その他直接実行されたときにだけ走らせたい処理

__name__ は組み込みの変数で
  • pythonコマンドで直接実行されているとき "__main__"
  • 他のコードからimportされているとき そのファイル名
を文字列として格納しています。

そのため、 if __name__ == "__main__" が True となるのはそのコードが直接実行されたときのみ、となるので、 __name__ によるこのような場合分けが可能となります。


参考
python - What does `if __name__ == "__main__":` do? - Stack Overflow

2013/12/07

Python Tips:Sublime Text 2のビルドに特定のPython環境を使いたい

Sublime Text 2 でビルドに使うPython環境を指定する方法をご紹介します。

Sublime Text 2 では、Command+BやCtrl+Bで編集中のPythonスクリプトを実行することができます。

その際デフォルトではpythonコマンドがそのまま使われますが、設定ファイルを編集することによりスクリプト実行に使うPython環境を指定することができます。

該当する設定ファイルは「Python.sublime-build」です。たとえば、この設定ファイルの内容を以下のとおりに変更すると・・・
{
  // "cmd": ["python", "-u", "$file"],
  "shell": true,
  "cmd": ["source ~/.bashrc && workon name_of_virtualenv && python -u \"$file\""],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python"
}
virtualenvwrapperで作成した特定のpython環境が使われる形となります。

ただしこの場合、前提として

  1. virtualenvwrapperがインストールされている
  2. virtualenvwrapper有効化スクリプトが.bashrcに書き込まれている

の2点をおいています。

また、name_of_virtualenvのところには使いたいvirtualenv環境名を記入します。


以上です。

ここでご紹介したのはvirtualenvwrapperを使う場合ですが、virtualenvwrapperを使わずに直接変更する場合は

  • 環境変数PATHの先頭に使いたいPython環境へのパスを追加
  • python -uのところのpythonを使いたいPython環境のpythonに変更

するとよいでしょう。

ちなみに、Sublime Textの設定ファイルを触ったことがあまりない方は、念のためにバックアップを取ってからの編集がおすすめです。


Build Systems — Sublime Text Unofficial Documentation

2013/12/04

Python Tips:csvファイルを扱いたい

Pythonでcsvファイルを扱う方法をご紹介します。

csvを扱うには、名前もそのままの csv ライブラリが便利です。

csvを使うおおまかな流れは次のような形となります。
  1. ファイルオブジェクトを開く
  2. ファイルオブジェクトをcsv.reader()かcsv.writer()に渡す
  3. 生成されたcsvオブジェクトのメソッドを使う
  4. ファイルオブジェクトを閉じる

いちばんシンプルなcsvの読み込みは次のような感じになります。
import csv
with open(FILENAME, 'r') as f:
    c = csv.reader(f)
    for row in c:
        print row
この場合は各rowが配列として帰ってきます。デフォルトでは各要素は文字列なので、数値として扱いたいときには
    for row in c:
        num_row = map(float, row)  # 各要素をfloatに変換
        print num_row
などとするとよいかと思います。

先頭行がデータではなくカラム名になっているかどうかを調べたいときは、csv.Snifferクラスのhas_header()メソッドにファイルの中身を渡すこととある程度自動判定してくれます。
    hasHeader = csv.Sniffer().has_header(f.read(100))
    if hasHeader:  # header行があるかどうか True/False
        print "%s has a header" % FILE
        f.seek(0)  # 先頭に戻る

書き込みの方法や区切り文字の指定方法等々は csvライブラリ のところにも書いたので、よろしければそちらもご覧ください。

csvライブラリ