JF1DIR業務日誌(はてなblog版)

アマチュア無線局JF1DIRのアクティビティをつづっています。

Pandasでログ統計処理スクリプト(その3)

コールサイン文字列から国内エリアと日本以外の局を判別する関数です。あまり厳密な方法ではないです(例えば/MMやゲストオペレータのコールなど)。python正規表現のマッチングがシンプルに書けないし、後方参照が仕方がまどろっこしいので困ったものです。

def call_area(callsign):
	jacall_re = re.compile('(^J[AD-S]\d)|(^[78][J-N]\d)')
	area_ja_re = re.compile('^J[AD-S](\d)')
	area_7_re = re.compile('^7[K-N][1-4]')
	area_7j_re = re.compile('^7J(\d)')
	area_8_re = re.compile('^8[J-N](\d)')
	area_jd1_re = re.compile('^JD1')
	call_sp = callsign.split('/')
	if len(call_sp) >= 2:
		if jacall_re.search(call_sp[0]):
			m_areanum = re.match('(\d)', call_sp[1])
			if m_areanum:
				area = 'JA' + m_areanum.group(1)
			else:
				area = 'DX'
		else:
			area = 'DX'
	else:
		if jacall_re.search(callsign):
			m_ja = area_ja_re.search(callsign)
			m_7 = area_7_re.search(callsign)
			m_7j = area_7j_re.search(callsign)
			m_8 = area_8_re.search(callsign)
			if m_ja:
				area = 'JA' + m_ja.group(1)
			elif m_7:
				area = 'JA1'
			elif m_7j:
				area = 'JA' + m_7j.group(1)
			elif m_8:
				area = 'JA' + m_8.group(1)
		else:
			area = 'DX'
		m_jd1 = area_jd1_re.search(callsign)
		if m_jd1:
			area = 'DX'
	return area

この関数を使って例えば、

In [12]: call_area('JF1DIR')
Out[12]: 'JA1'

In [13]: call_area('JF1DIR/9')
Out[13]: 'JA9'

In [14]: call_area('KH0/JF1DIR')
Out[14]: 'DX'

とエリアに対応する文字列を返します。前回の(その2)でこの関数を使って新たに'area'列にエリア文字列の列を作っておきました。
さて、ここからが本番です。
まずはこのログからどのバンドの運用があったのかを調べるのは、

In [15]: set(log.freq)
Out[15]: {3.5, 7.0, 14.0, 21.0, 28.0}

とします。log.freqでfreq列のリストが得られ、そのset(重複を除いたリスト)を表示させればよいのです。
次に、ログのすべてのデータから、エリア別の統計を取りたい場合は、

In [16]: log.area.value_counts()
Out[16]: 
DX     419
JA1     29
JA3     12
JA2     11
JA0      9
JA7      7
JA6      6
JA4      4
JA8      2
JA5      1
Name: area, dtype: int64

で得られます。python/pandasの偉いところはIPythonのインタラクティブ表示やスクリプト内のprint()文で表示させるとちゃんと整形されて表示されるということです。ちなみに、このメソッドの返り値はSeries型になっているので、

In [17]: a = log.area.value_counts()

In [18]: a[0]
Out[18]: 419

In [19]: a.index[0]
Out[19]: 'DX'

で個々の値に参照することができます。
例えば7MHzの運用分だけでエリア別統計を取りたい場合は、freq列にフィルターをかけてカウントします。

In [20]: log[log.freq==7].area.value_counts()
Out[20]: 
DX     59
JA1    16
JA2     7
JA3     6
JA0     5
JA7     5
JA4     1
JA6     1
JA5     1
Name: area, dtype: int64

QSO数だけならば、

In [21]: log[log.freq==7].area.count()
Out[21]: 101

でよいです。
QSO時間でログを切り取る、例えば、2012年5月26日18時〜24時までのデータだけを切り出す場合は、

In [22]: log[(log.datetime>=datetime(2012,5,26,18,0,0,0))&(log.datetime

のようにします。
call列をキーにログ全体をソートするには、

In [23]: log.sort('call').head()
Out[23]: 
       call            datetime  urrst  myrst  freq mode  urnr  mynr area
66    3V8BB 2012-05-26 06:25:00    599    599  21.0   CW    67   503   DX
33     3Z2X 2012-05-26 05:20:00    599    599  21.0   CW    34    68   DX
41    6M0NR 2012-05-26 05:32:00    599    599  21.0   CW    42   172   DX
165   6M0NR 2012-05-26 11:38:00    599    599   7.0   CW   167   192   DX
141  7J1YAJ 2012-05-26 10:55:00    599    599   7.0   CW   143  3039  JA1

ただし元のlogデータフレームには影響しない非破壊のメソッドです。
以上のようなデータフレームに対するメソッドを駆使すればやりたいことは大体出来てしまいます。位置から自分でプログラミングすると結構面倒ですが、上記メソッドをスクリプト内に組み込んであげれば各種統計解析が可能となります。いかがでしょう?pythonの概念がわかってしまうと意外と簡単ですよね。