Tommy Heartbeat 2nd RSSフィード Twitter

2013-12-25

[] ブログ移転のお知らせ

Sphinx を使ってブログを書きたい! ということで、移転することにした。

2013-12-24

[] Python で解析 24

“Advent Calendar 2013 - Python で解析!” の二十四日目。実データの操作。

今回は、実際のデータを探ってみたい。ここまでのデータ操作を使って、簡単にできるところまで。

一応、これが Advent Calendar 2013 の最後。技術系の Advent Calendar では 25 までやるのが慣例のようだが、 Wikipedia で調べたら、 24 までと書いてあったからね!

1. 実データの取得

実データの取得がなかなか難しい…。ブログのネタ用に、データを探してみたのだが、なかなか都合のいいのが見つからない。ネタ的に、野球やサッカーなんかがよかったのだが、 PDF だったり、グラフ化された Web の記事だったり…で、 Pandas を試してみるのに、ちょうどいいのがなかなか見つからない。

政府系の Open Data の取り組みも、 Excel にレポート形式でまとめられてて、ちょっと加工してやらないと…といった感じで、軽いブログ記事でサクッと使うには、なかなか難しい。"DATA GO JP" ってのがあるのだが、フォーマットでは PDF が 4891 件でダントツの一位。う〜む、使えん…。CSV も 389 件あるが、放射濃度のデータがたくさんあって、クリスマスイブに取り上げるのもなぁ…。そんな中でも、レッドリストが使いやすくて、 Pands の練習にはちょうどいい感じ。テーマ的には、クリスマスイブなのに…とは思うが、探しまわるのに疲れたので、これで行くことにする。

import pandas as pd
In [1]: import pandas as pd

In [2]: df = pd.read_csv('http://www.sizenken.biodic.go.jp/va2007/va_2007.csv', encoding='Shift-JIS')

こんな感じで、 URL から直接 DataFrame が作れるのが便利。これで、クリスマスイブっぽいのが、見つかれば良かったのだが…。

2. 概要の把握

まずは、全体像を。

In [3]: df
Out[3]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 13498 entries, 0 to 13497
Data columns (total 11 columns):
種コード                     13498  non-null values
科名                       13498  non-null values
和名                       13498  non-null values
学名                       13498  non-null values
都道府県コード                  13498  non-null values
都道府県                     13498  non-null values
平成12年レッドデータブック刊行時のランク    13498  non-null values
平成19年レッドリスト選定時のランク       13498  non-null values
2万5千地形図名                 13315  non-null values
メッシュコード                  13498  non-null values
生育状況                     13498  non-null values
dtypes: float64(1), int64(2), object(8)

11 項目あって、int64 が 2 件しかないので、数値データではなく文字データのようだ。13,497 件のレコードがあり、ほとんどの項目に値が入っているが、"2万5千地形図名" には欠損があるようだ。"2万5千地形図名" を詳しくは知らないが、市町村名ぐらいで位置情報を特定できるものだと思われる。

軽く各項目の値も見ておく。

In [4]: df.head()
Out[4]:
    種コード     科名        和名                               学名  都道府県コード 都道府県  \
0   9240    ヒノキ  リシリビャクシン  Juniperus communis var. montana        1  北海道
1  60990  ヒルムシロ   リュウノヒゲモ           Potamogeton pectinatus        1  北海道
2   9240    ヒノキ  リシリビャクシン  Juniperus communis var. montana        1  北海道
3   9240    ヒノキ  リシリビャクシン  Juniperus communis var. montana        1  北海道
4   9240    ヒノキ  リシリビャクシン  Juniperus communis var. montana        1  北海道

  平成12年レッドデータブック刊行時のランク 平成19年レッドリスト選定時のランク 2万5千地形図名  メッシュコード    生育状況
0                    EN                 VU    鷹泊貯水池   654270  現存(生育)
1                    VU                 NT       鵡川   634167  現存(生育)
2                    EN                 VU      布部岳   644272  現存(生育)
3                    EN                 VU     胆振福山   644222  現存(生育)
4                    EN                 VU     神居古潭   654241  現存(生育)

11 項目もあるので、横には並べられなくて、分割して表示されている。横に並べて綺麗に見るには、 Excel とか…を使う方がいいだろう。

3. 欠損データの傾向調査

試しに、"2万5千地形図名" が入っていない都道府県を調べてみる。

In [5]: df[pd.isnull(df[u"2万5千地形図名"])][u'都道府県'].value_counts()
Out[5]:
鹿児島県    58
東京都     19
静岡県     18
熊本県     13
大分県      8

ブログの都合上、トップ 5 だけ抜粋した。

で、きっと、生息地域の偏りと同じに違いない…と思って、全体の都道府県を調べてみる。

In [6]: df[u'都道府県'].value_counts()
Out[6]:
北海道     798
岡山県     674
大分県     596
愛知県     537
山形県     533
宮城県     527
千葉県     519
長野県     453
三重県     437
鹿児島県    380

こっちはトップ 10 まで掲載してみたが、関連があるようには見えない。 念のため、統計的に調べてみる。

In [7]: s1 = df[u'都道府県'].value_counts()

In [8]: s2 = df[pd.isnull(df[u"2万5千地形図名"])][u'都道府県'].value_counts()

In [9]:

In [9]: df1 = pd.DataFrame({u'都道府県1': s1})

In [10]: df2 = pd.DataFrame({u'都道府県2': s2})

In [11]: tmp = df1.join(df2)

In [12]: tmp[u'都道府県2'] = tmp[u'都道府県2'].fillna(0) # 欠損データがなかったところに 0 を補完する。

In [13]: tmp.corr() # 相関係数
Out[13]:
          都道府県1     都道府県2
都道府県1  1.000000  0.163086
都道府県2  0.163086  1.000000

相関係数 0.16 ってことで、生息地域の偏りとは関係なさそうだ。

品種によって生息地が特定しにくい…とかあるのだろうか?

In [14]: s1 = df[u'科名'].value_counts()

In [15]: s2 = df[pd.isnull(df[u"2万5千地形図名"])][u'科名'].value_counts()

In [16]:

In [16]: df1 = pd.DataFrame({u'科名1': s1})

In [17]: df2 = pd.DataFrame({u'科名2': s2})

In [18]: tmp = df1.join(df2)

In [19]: tmp[u'科名2'] = tmp[u'科名2'].fillna(0) # 欠損データがなかったところに 0 を補完する。

In [20]: tmp.corr() # 相関係数
Out[20]:
          科名1       科名2
科名1  1.000000  0.778866
科名2  0.778866  1.000000

相関係数 0.78 ってことで、正の相関があるようなのだが、納得するには、もう一歩。

In [21]: df1.head(10)
Out[21]:
         科名1
キク      1605
カヤツリグサ   909
ゴマノハグサ   850
シソ       838
タヌキモ     801
タデ       628
ガガイモ     582
トチカガミ    533
ヒルムシロ    460
ミクリ      386
In [22]: df2.head(10)
Out[22]:
        科名2
カヤツリグサ   23
キク       20
シソ       12
アカネ      10
ゴマノハグサ    9
ガガイモ      8
ヤブコウジ     7
セリ        6
ミクリ       5
メシダ       5

両方の科名の重複状況から考えて、品種によって…というよりも、品種の多いものが "2万5千地形図名" の欠損が多いということなのだろう。財布を落としやすい名字を調べたら、全国の名字の多い順と類似していた…といったような、まあ、妥当な欠損状況のようだ。

…と、こんな感じで Pandas を活用して、データを調べられる訳だ。

4. 歯切れの悪い終わり方だが…

まだ、調査開始したばかりで、いきなり終わるのも気が引けるが、Advent Calendar の目的が Pandas の機能調査なので、ここまでとしたい。

あわよくば "マクドナルド対コンビニの垣根を越えた戦争が始まっている" みたいなレポートが出せれば良かったのだが、そう簡単には行かない。深堀してゆくには、それなりに時間がかかる (そして、実際のデータマイニングも、こーゆー地道な作業の積み重ねだったりするのだけど) …。

今回はこんなところで。

2013-12-23

[] Python で解析 23

“Advent Calendar 2013 - Python で解析!” の二十三日目。DataFrame - 16

今回は Period について。Timestamp が時刻、Period が時間を取り扱う。時間というより、期間の方が語感としては近いかもしれない。

1. Period

だいたいの言語で、日付型や時刻型は用意されているが、期間を表すオブジェクトというのは、珍しい気がする。日付型なり、時刻型なり…を使って、開始日、終了日を自分で管理するのに慣れてしまっているだけかもしれないが…。

まずは 2013 年。

In [1]: import pandas as pd

In [2]: p1 = pd.Period('2013')

In [3]: p1
Out[3]: Period('2013', 'A-DEC')

In [4]: p1.start_time
Out[4]: Timestamp('2013-01-01 00:00:00', tz=None)

In [5]: p1.end_time
Out[5]: Timestamp('2013-12-31 23:59:59.999999999', tz=None)

2013年1月。

In [6]: p2 = pd.Period('2013-01')

In [7]: p2
Out[7]: Period('2013-01', 'M')

In [8]: p2.start_time
Out[8]: Timestamp('2013-01-01 00:00:00', tz=None)

In [9]: p2.end_time
Out[9]: Timestamp('2013-01-31 23:59:59.999999999', tz=None)

日単位や時単位の Period も作れるのだが、文字列をうまくパースしてくれない。リファレンスのサンプルには載っているのだが、やってみるとエラーが表示される。代替として、個別のパラメーターを指定して作成してみることにする。

In [10]: p3 = pd.Period(year=2013, month=12, day=24, freq='D')

In [11]: p3
Out[11]: Period('2013-12-24', 'D')

In [12]: p3.start_time
Out[12]: Timestamp('2013-12-24 00:00:00', tz=None)

In [13]: p3.end_time
Out[13]: Timestamp('2013-12-24 23:59:59.999999999', tz=None)

時単位の場合。

In [14]: p4 = pd.Period(year=2013, month=12, day=24, hour=9, freq='H')

In [15]: p4
Out[15]: Period('2013-12-24 09:00', 'H')

In [16]: p4.start_time
Out[16]: Timestamp('2013-12-24 09:00:00', tz=None)

In [17]: p4.end_time
Out[17]: Timestamp('2013-12-24 09:59:59.999999999', tz=None)

Period は、自由な期間 (任意の開始時刻、終了時刻) を表現することはできない。1 時間、1日、ひと月、1四半期などの、用意されている範囲で活用するもののようだ。

2. PeriodIndex

Period のリストを渡して作ってみる。

In [18]: pi1 = pd.PeriodIndex([pd.Period('2011'), pd.Period('2012'), pd.Period('2013')])

In [19]: pi1
Out[19]:
<class 'pandas.tseries.period.PeriodIndex'>
freq: A-DEC
[2011, ..., 2013]
length: 3

In [20]: type(pi1)
Out[20]: pandas.tseries.period.PeriodIndex

開始、終了を指定して作ってみる。

In [21]: pi2 = pd.PeriodIndex(start='2013-1', end='2013-12', freq='M')

In [22]: pi2
Out[22]:
<class 'pandas.tseries.period.PeriodIndex'>
freq: M
[2013-01, ..., 2013-12]
length: 12

月間や年間の集計などの用途で活用できそうだ。

3. 活用例

PeriodIndex を使った DataFrame を作ってみる。

In [23]: df = pd.DataFrame({
   ....:  'uu': [1000, 2000, 3000, 4000, 5000, 60000]
   ....: }, index=pd.PeriodIndex(start='2012-10', end='2013-3', freq='M'))

In [24]: df
Out[24]:
            uu
2012-10   1000
2012-11   2000
2012-12   3000
2013-01   4000
2013-02   5000
2013-03  60000

2012 年分だけ抽出する。

In [25]: df['2012']
Out[25]:
           uu
2012-10  1000
2012-11  2000
2012-12  3000

2012年12月分だけ抽出する。

In [26]: df['2012-12']
Out[26]:
           uu
2012-12  3000

ロジックを書かずに、こーゆーことができるのが便利だ。

In [27]: df[df.index >= pd.datetime(2012, 12, 15)]
Out[27]:
            uu
2012-12   3000
2013-01   4000
2013-02   5000
2013-03  60000

イコールを含まない場合はこうなる。

In [28]: df[df.index > pd.datetime(2012, 12, 15)]
Out[28]:
            uu
2013-01   4000
2013-02   5000
2013-03  60000

直感的な振る舞いではない気もするが、妥当な振る舞いのような気もする。ま、どちらかと言えば、事例のフィルター条件があまりよろしくないので、実際に活用する場合は、分かりやすいフィルター条件を心がけたいところ。

今回はこんなところで。

2013-12-22

[] Python で解析 22

“Advent Calendar 2013 - Python で解析!” の二十二日目。DataFrame - 15

今回はカテゴリーデータの扱いについて。カテゴリーデータというのは大きさを表す数字ではなく、種類を表すもので、例えばどのブラウザからのアクセスが多いか? のような時に活用する。

1. データの準備

では、いつもの通りのデータの準備を。

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...:    'accessed': ['2013-12-21', '2013-12-21', '2013-12-21', '2013-12-22', '2013-12-22'],
   ...:    'user_id': [14600, 29000, 8800, 12100, 14600],
   ...:    'browser': ['IE', 'IE', 'Safari', 'Chrome', 'IE']
   ...: })

In [3]: df
Out[3]:
     accessed browser  user_id
0  2013-12-21      IE    14600
1  2013-12-21      IE    29000
2  2013-12-21  Safari     8800
3  2013-12-22  Chrome    12100
4  2013-12-22      IE    14600

2. value_counts

カテゴリーデータの数を数える時は value_counts を使う。まずは、ブラウザ別。

In [4]: df.browser.value_counts()
Out[4]:
IE        3
Safari    1
Chrome    1
dtype: int64

次は日別。

In [5]: df.accessed.value_counts()
Out[5]:
2013-12-21    3
2013-12-22    2
dtype: int64

そして、日毎のブラウザ別。

In [6]: df.groupby('accessed').browser.value_counts()
Out[6]:
accessed
2013-12-21  IE        2
            Safari    1
2013-12-22  IE        1
            Chrome    1
dtype: int64

3. stack, unstack

日毎のブラウザ別のヤツは、数が多くなって来ると、どのブラウザのアクセスが多いのか? とか、増加傾向にあるのか、減少傾向にあるのか? といったことが見づらくなる。そこで、ブラウザをカラムにして、見やすくしたい…という場合は unstack を使う。

In [7]: df.groupby('accessed').browser.value_counts().unstack()
Out[7]:
            Chrome  IE  Safari
accessed
2013-12-21     NaN   2       1
2013-12-22       1   1     NaN

unstack の逆は stack で、実は、元のデータを stack 表示することもできる。

In [8]: df.stack()
Out[8]:
0  accessed    2013-12-21
   browser             IE
   user_id          14600
1  accessed    2013-12-21
   browser             IE
   user_id          29000
2  accessed    2013-12-21
   browser         Safari
   user_id           8800
3  accessed    2013-12-22
   browser         Chrome
   user_id          12100
4  accessed    2013-12-22
   browser             IE
   user_id          14600
dtype: object

stack <-> unstack は DataFrame <-> Series の相互変換になっていて、 Series にする時には SQL でいうところの複合キーが生成されている。

In [9]: df.stack().index
Out[9]:
MultiIndex
[(0, u'accessed'), (0, u'browser'), (0, u'user_id'), (1, u'accessed'), (1, u'browser'), (1, u'user_id'), (2, u'accessed'), (2, u'browser'), (2, u'user_id'), (3, u'accessed'), (3, u'browser'), (3, u'user_id'), (4, u'accessed'), (4, u'browser'), (4, u'user_id')]

カラムが第二キーなっているのが分かる。

今回はこんなところで。

2013-12-21

[] Python で解析 21

“Advent Calendar 2013 - Python で解析!” の二十一日目。DatetimeIndex

今回は DatetimeIndex について。

1. データの生成

DatetimeIndex を生成するには、 date_range を使う。試しに DatetimeIndex を使った DataFrame を生成してみる。

In [1]: import pandas as pd

In [2]: df1 = pd.DataFrame({
   ...:   'value': range(18),
   ...: }, index=pd.date_range('2012/9/1', '2013/8/31', freq='3W'))

In [3]: df1
Out[3]:
            value
2012-09-02      0
2012-09-23      1
2012-10-14      2
2012-11-04      3
2012-11-25      4
2012-12-16      5
2013-01-06      6
2013-01-27      7
2013-02-17      8
2013-03-10      9
2013-03-31     10
2013-04-21     11
2013-05-12     12
2013-06-02     13
2013-06-23     14
2013-07-14     15
2013-08-04     16
2013-08-25     17

2. date_range の説明

date_range だけを実行してみると、次のようになる。

In [4]: pd.date_range('2012/9/1', '2013/8/31', freq='3W')
Out[4]:
<class 'pandas.tseries.index.DatetimeIndex'>
[2012-09-02 00:00:00, ..., 2013-08-25 00:00:00]
Length: 18, Freq: 3W-SUN, Timezone: None

DatetimeIndex が返って来る。指定するのは開始、終了、頻度で、上の例だと 2012/9/1 から 2013/8/31 までの範囲で三週間毎の Timestamp 生成を指定していることになる。 freq は省略可能。

In [5]: pd.date_range('2012/9/1', '2013/8/31')
Out[5]:
<class 'pandas.tseries.index.DatetimeIndex'>
[2012-09-01 00:00:00, ..., 2013-08-31 00:00:00]
Length: 365, Freq: D, Timezone: None

省略時は 'D' (日毎) の Timestamp が生成されることが Length が 365 になっていることから分かる。freq には週毎 'W'、時間毎 'H'、 12 時間毎 '12H' …などなど、いろいろ指定でき、月末日 'BM' なんかも便利そうだ。

タイムゾーンも指定できる。

In [6]: pd.date_range('2012/9/1', '2013/8/31', tz='Asia/Tokyo')[0]
Out[6]: Timestamp('2012-09-01 00:00:00+0900', tz='Asia/Tokyo')

3. 便利なフィルター

DatetimeIndex を使うと、こんなことができる。

In [7]: df1['2012']
Out[7]:
            value
2012-09-02      0
2012-09-23      1
2012-10-14      2
2012-11-04      3
2012-11-25      4
2012-12-16      5

最初に作った DataFrame 中から 2012 年のものだけを抽出できた。 2013 年分も同様に抽出できるし、ある月のものを抽出することも簡単だ。

In [8]: df1['2013-03']
Out[8]:
            value
2013-03-10      9
2013-03-31     10

"date >= '2013/3/1' and date < '2013/4/1'" のような条件文を書かなくていいのは、なかなか便利だ。

今回はこんなところで。