天気のスコアリングをやってみた

今回は技術系のお話。「いい天気」「悪い天気」を数値で表してみよう、というものになります。評価値が感覚に合うかどうかは保証できません。ご了承を。

気象庁のデータを使おう

さて元となる天気のデータですが、ありがたいことに気象庁がcsv形式でダウンロードできるようにしてくれています。

今回は2019年の大阪地点の気象データを使います。ここの「天気概況」から

快晴
曇後一時雨
曇時々晴一時雨

のようなデータを得ることができます。今回はこれを対象とします。

概況の規則を見つける

天気概況の表現には決まりがあります。引用すると

「基本天気用語」(必須) + 「天気変化用語」(0~3語) + 「伴う現象」(0~1語)

となっています。伴う現象に関しては今回は省き、純粋に晴れや曇りといった要素で判断することにします。

具体的に見ていきましょう。

基本天気用語:晴、雨といった天気の状態を表す単語です。
天気変化用語:後、時々といった天気の変化を表す単語です。必ず天気の状態を表す単語が続きます。

こう見ると、概況は数学的に表現できそうです。定義してみましょう。

天気≔快晴|晴|薄曇|曇|大雨|雨|大雪|雪|みぞれ
移り変わり≔後一時|後時々|後
混合≔一時|時々

天気変化用語の「後」には一日を前半と後半に分けるという意味合いがありますから、それを考慮して天気変化用語を二つに分けています。ここまでくると、天気概況の表現を記号的に考えることができます。

快晴 = <天気>
曇後一時雨 = <天気><移り変わり><天気>
曇時々晴一時雨 = <天気><混合><天気><混合><天気>

このように記号的に表現すると、ある構造が見えてきませんか?そう、四則演算の構造と非常に似通っています。<天気>が数字、<移り変わり><混合>は演算子と見ればイメージが付くのではないでしょうか。

演算子の優先度とBN記法

四則演算の演算子には優先度があります。掛け算・割り算を先に計算する、というものでしたね。これをプログラムとして書ける表現するために、バッカス・ナウア記法(以下BNF,これを拡張した記法をEBNFと呼ぶ)と呼ばれるものを利用します。

四則演算に関して、EBNFを利用すると次のようになります。(本当は「数字」もきちんと定義する必要がありますが今回は割愛)

expression := <term>[(+|-)<term>]*
term := <factor>[(*|/)<factor>]*
factor := <number> | '(' <expression> ')'
number := 1つ以上の数字

ややこしく見えますが、「式」は項[(+orー)項]([]内は0回以上の繰り返し)で表現され、「項」は因子[(×or÷)因子]で表現され…と定義を行っています。このように書くと、複雑な式も表現することができます。例えば…

1+2*3*(4-5) = <term>+<term>
            = <factor> + <factor> * <factor> * <factor>
            = <number> + <number> * <number> * ( <expression> )
            = 数字 + 数字 * 数字 * ( <term> - <term> )
            = 数字 + 数字 * 数字 * ( <factor> - <factor> )
            = 数字 + 数字 * 数字 * ( <number> - <number> )
            = 数字 + 数字 * 数字 * ( 数字 - 数字 )

なんとなくイメージが沸きましたでしょうか。これを先の天気概況にも使います。幸い天気概況には括弧はありませんので、もう少しシンプルに書くことができます。

expression := <term>[<transition><term>]*
term := <weather>[<mixed><weather>]*
weather := 快晴|晴|薄曇|曇|大雨|雨|大雪|雪|みぞれ
transition := 後一時|後時々|後
mixed := 一時|時々

これでプログラムで実装する準備は整いました。…と、その前に数値化するためにそれぞれの値・機能を決めておきましょう。天気については辞書(連想配列)で定義します。雨や雪はその日を「悪い天気」と印象付けやすいため、重めに設定します。

{"快晴": 0, "晴": 1, "薄曇": 4, "曇": 4,"大雨": 16, "雨": 9, "大雪": 16, "雪": 9, "みぞれ": 9}

天気変化用語に関しては、その前後の天気の割合を決める演算子として考えて以下のように設定します。

A後B := (A*5 + B*5)/10
A後一時B := (A*9 + B)/10
A後時々B := (A*8 + B*2)/10
A一時B := (A*8 + B*2)/10
A時々B := (A*6 + B*4)/10

では実装してみましょう。今回もPythonを使いました。

実装と実行結果

import csv
import re

result = []
pos = 0
weather = "快晴|晴|薄曇|曇|大雨|雨|大雪|雪|みぞれ"
change = "後一時|後時々|一時|時々|後"
pattern = f"({weather}|{change})"
weather_dict = {"快晴": 0, "晴": 1, "薄曇": 2, "曇": 2,"大雨": 4, "雨": 3, "大雪": 4, "雪": 3, "みぞれ": 3}

def weather_(lst):
   global pos
   pos = 0
       
   return expr(lst)

def expr(w_list):
   global pos
   v = term(w_list)
   while pos < len(w_list) and (w_list[pos] == "後一時" or w_list[pos] == "後時々" or w_list[pos] == "後"):
       op = w_list[pos]
       pos = pos + 1
       if op == "後一時":
           v = (v*9 + term(w_list))/10
       elif op == "後時々":
           v = (v*8 + term(w_list)*2)/10
       elif op == "後":
           v = (v*5 + term(w_list)*5)/10
           
   return v
           
def term(w_list):
   global pos
   v = factor(w_list)
   while pos < len(w_list) and (w_list[pos] == "一時" or w_list[pos] == "時々"):
       op = w_list[pos]
       pos = pos + 1
       if op == "一時":
           v = (v*8 + factor(w_list)*2)/10
       elif op == "時々":
           v = (v*6 + factor(w_list)*4)/10
   
   return v
           
def factor(w_list):
   global pos
   v = weather_dict.get(w_list[pos])
   pos = pos + 1
   
   return v
   

with open('./weather.csv', newline='', encoding='utf-8-sig') as csvfile:
   read = csv.reader(csvfile)
       
   for row in read:
       for day in row:
           all_matches = re.findall(pattern, day)
           score = weather_(all_matches)
           result.append(score)
         
with open('./weather_results.csv', 'w', newline="", encoding='utf-8-sig') as csvfile:
   writer = csv.writer(csvfile)
   writer.writerow(result)

以上のようなコードになっています(名前が異なりますがfactor関数は実質的にweatherを得るためだけの関数です)。では実行結果を見てみましょう。

晴後曇,晴時々曇,晴後一時曇,晴後時々曇,曇時々晴一時雨,曇後時々晴,晴,薄曇後晴,晴後一時曇,曇,曇後一時晴,曇,晴,晴,曇時々雨,晴一時曇,曇時々晴,晴時々曇,晴,雨後晴時々曇,晴後時々曇
2.5,2.2,1.3,1.6,4.04,3.4,1,2.5,1.3,4,3.7,4,1,1,6.0,1.6,2.8,2.2,1,5.6,1.6

画像1

うまく値を導出できていますね。ヒストグラムでも見てみましょう。

画像2

晴れとなる評価値1からなだらかな曲線を描いていますね。データ数を増やせばより正確な分布が見れそうです。というわけで期間を2009年~2019年のデータまで増やしてみましょう。

画像3

「くもり」と判定される日が多いのが原因で評価値4の部分が飛び出ていますが、それ以外は先ほどよりなだらかな線を描いていますね。そしてこの曲線は重みを「1-4-9-16」にしたことに原因がありそうです。重みを線形にして見てみます。

{"快晴": 0, "晴": 1, "薄曇": 2, "曇": 2,"大雨": 4, "雨": 3, "大雪":4, "雪": 3, "みぞれ": 3}

画像4

傾きが線形に近くなりましたね。天気はある一定の比を保って現れていると言えそうです。もう少し掘ってみても良いのですが、今回は「いい天気」「悪い天気」を評価することが目的だったのでこの辺で終わることにします。

おわりに

「これ表記的に四則演算と同じでいけそう→BN記法って学部の授業でやったことあるけどどうやるんだっけ…」で詰まりかけた今回のスコアリングですが、結構良い感じのデータが取れたので良かったかなと。天気を値として考えることで「いい天気の時はXXが売れる」「悪い天気の時はYYが売れる」みたいな分析ができないかな、と思っています。結構アバウトに考えている部分がありますから、何か間違い等あれば指摘していただけると助かります。

いいなと思ったら応援しよう!