見出し画像

pythonで地図を作って可視化してみる(1)-MLBの観客、どこに多い?-

前回投稿の最後で地図を紹介しましたが、今回はそこに焦点を合わせます。
そこまでのデータ準備(web scraping)は前回と同じものを使っているので、そちらもご参考下さい。
なおこのnoteはその作成時の日付(このnoteは2022/8/19)で都度作成しています。毎日試合は実施されていますので、データも自動で更新されています(コード化のメリットでもあります)。

球団名から球場の位置を知る(ジオコーディング)

まず、各球団の本拠地球場の住所をwikipediaのList of current Major League Baseball stadiumsを参考にし手調べます。ここでも取り込みはpandasを使います。やり方は同じです。

url = 'https://en.wikipedia.org/wiki/List_of_current_Major_League_Baseball_stadiums'
dfStadium = pd.read_html(url)[1]
dfStadium.head()
pandasで取り込んだMLB本拠地球場のリスト

Nameの列に球場名がありますが、ここから球場の緯度経度座標を求めてみます。球団名だと球団事務所の住所になったりするためです。
このように地名や施設名などから住所の地理的座標を求めることをジオコーディング、Geocodingといいます。
スマホですぐに地図が使える時代に、わざわざこの方法を使うのは業者や専門家だけだと思いますが、それが自分の書いたコードでもできるというのは、冷静に考えたらすごい話です。
なおこの逆で、緯度経度から地名や住所を求めることを、リバースジオコーディングと言ったりします。

地理情報を扱うことになるので、pythonではGeoPandasをimportします。
GeoPandasはPandasの拡張で、Pandasと近い構文を持ってますのでそれから入った人はとっつきやすいと思います。

ジオコーディングの実装方法は色々あるのですが、ここではgpd.tools.geocodeを使います。詳細はリンク先をご覧ください。
このジオコーディングリクエストには上限(1日にできる件数など)があるので、サービプロバイダの制限をよくご覧ください。大量にそのような要件がある場合は商用サービスを使うことになります。
コードの引数は以下のようなものです。最低第1引数は必要で、あとはオプションになります。

  • 第1引数 ジオコーディングに使用する名称リスト

  • 第2引数 ジオコーディングに使用するプロバイダー(デフォルトはphoton)、今回はNominatimを使用(理由は後述)。

  • 第3引数 user_agentは自分のサービス名などを入れます。

  • 第4引数 処理がタイム・アウトする場合があるのでその予防で

import geopandas as gpd
#球場名から緯度経度座標をジオコーディングで求める
g_stadium_locations = gpd.tools.geocode(dfStadium['Name'], provider='nominatim', user_agent='cgisj', timeout=4)
g_stadium_locations.head()

結果はこんな感じになります。geometryに経度、緯度の10進数表記で位置情報が求まります。アメリカは西経にあるため、経度にマイナスの値がついています。そしてadddressに住所が入ります。あるあるですが外国では住所は通りの名前になるので、だいぶとっつきにくいです。慣れればそっちのほうが良いんだと思いますが。

球場名によるジオコーディング結果

球場の位置が違う?

ここで問題がおきました。結果の表の2番目はエンゼルスのAngel Stadiumを示したものですが、座標が-104.81110,39.50066になっています。
これでは4番目にあるアリゾナ・ダイアモンドバックスの座標-112.06669 33.44549よりも東にあることになってしまいます。
わかる人はあまりいないかもしれませんが、エンゼルスはLAにあるので明らかな間違いだとわかります。GoogleMapでみてみると、この場所はコロラドのEchoPark Stadiumのようです。なぜ?

ここで、振り返ってwikipediaの球場名を見てみると、Angel Stadiumしか書いていません。リンク先ではとAngel Stadium of Anaheimとあります。そこで球場名をpandasで変更して、再度ジオコーディングしてみます。

dfStadium.iloc[1,1]='Angel Stadium of Anaheim' #球場名を変更
#再度ジオコーディングを実施
g_stadium_locations = gpd.tools.geocode(dfStadium['Name'], provider='nominatim', user_agent='cgisj', timeout=4)
g_stadium_locations.head()
球場名によるジオコーディング結果 エンゼルスの球場名変更後

座標は-117.88174,33.80026になっています。Googlemapで確かめても、うまくいったようです。最初からGoogleMapでみたら?という話ですが。

ジオコーディングにはこういう例がたまにありますので注意して下さい。
実は最初、プロバイダーとしてphotonを使ったのですが、位置をアメリカ以外にしてしまったり、位置がわからないと言う結果が出たりしたので、nominatimにきりかえたのでした。
余談ですが私はこの駐車場で停めていた車を探せず迷子になったことがあります。野球観戦に来ていた現地の高校生に助けてもらいました。

位置情報に観客数のデータをリンクして1つのデータにする

このジオコーディング結果には球場の場所と住所があるだけで、wikipediaにあった球場名や球団名などの情報がありません。そこで両者のデータをリンクします。同じ並びになっているので行indexのリンクで可能です。
その前に、チーム名の英語表記(Team)が少し長いのと、前回までに作った観客数のデータとリンクできるようにチーム名の列も足しておきます。

#短縮名表記とリンク用にチーム名を追加
dfStadium['チーム']=['Milwaukee','LA Angels','St. Louis','Arizona','NY Mets','Philadelphia','Detroit','Colorado','LA Dodgers','Boston','Texas','Cincinnati','Chicago White Sox','Kansas City','Miami','Houston','Washington','San Francisco','Baltimore','San Diego','Pittsburgh','Cleveland','Oakland','Toronto','Seattle','Minnesota','Tampa Bay','Atlanta','Chicago Cubs','NY Yankees']
#地理情報と野球場情報をリンク
g_StadiumLocation=g_stadium_locations.merge(dfStadium,left_index=True,right_index=True)
g_StadiumLocation

球場の情報が位置情報の後ろに加わりました。

球場の位置と球場の情報をリンク

さらに、前回までに使用していたMLBの観客数データをチーム名をキーワードにしてリンクします。なお説明の都合上、列名には日本語を使っていますが、できれば、文字コードなどの不具合防止のため英語表記の方が無難です。

g_MLBAttendance=g_StadiumLocation.merge(df,on='チーム')
g_MLBAttendance.sort_values('ホームゲーム平均観客数',ascending=False).head()

結果は以下のとおりですが、順位以降の列に観客数の情報が加わりました。全ゲーム数を見ると微妙にデータが増えて更新されているのがわかります。

球場の情報に観客数のデータをリンク(2022/8/19現在)

いよいよ地図にして表示

ここからいよいよ地図を表示してみます。
地図にラベル(球団名をその場所に示すための文字、注記とも)もつけたいのでその準備をします。

g_MLBAttendance['coords'] = g_MLBAttendance['geometry'].to_crs(epsg=3857).apply(lambda x: x.representative_point().coords[:])
g_MLBAttendance['coords'] = [coords[0] for coords in g_MLBAttendance['coords']]
g_MLBAttendance.head()

コードの1行目、to_crs(epsg=3857)ですが、これは最終的に表記する地図の座標系に沿った座標に変換している部分です。epsg: 3857というのはGoogleがGoogleMapで採用した座標系で、Webmapでは標準となっています。OpenStreetmapやMicrosoftのBing Map、ESRI社のWebMAPなどでも使われています。
ジオコーディング結果では位置が緯度経度で示されている(epsg; 4326)ので、それを変換しているわけです。右端のcoords列です。この座標の位置にラベルがきます。

ラベルを置くための位置座標の計算

いよいよ地図表現です。
これも記載方法だったり、追加の細かい設定だったりは色々あるのですが、詳細はgeopandasのplotに関する記載などを御覧ください。

# ベースマップを追加し、ラベルを装飾するためのライブラリをインポート
import contextily as cx #ベースマップを使うためのライブラリ(python3.7以降)
import matplotlib.patheffects as path_effects

# 地図レイアウトの設定
# 緯度経度情報をepsg3857に変換して表示
# ホームゲーム平均観客数を丸の色と大きさ5分割(分割は自動)して表示
# 色はピンクから水色、透過率0.7、0.3の幅の赤の縁取り
# 凡例を地図の右側に表示
fig, ax = plt.subplots(figsize=(10, 6),dpi=200)
g_MLBAttendance.to_crs(epsg=3857).plot(ax=ax,column=g_MLBAttendance['ホームゲーム平均観客数'],markersize=g_MLBAttendance['ホームゲーム平均観客数']/100,\
                                       scheme='Quantiles',k=5,cmap="cool",linewidth=0.3,edgecolor='red',alpha=0.7,legend=True,\
                                      legend_kwds={'title':'ホームゲーム平均観客数 8/19','fontsize':'7','loc':'center left','bbox_to_anchor':(1,0.5),'fmt':'{:.0f}','title_fontsize':'6'})

# ベースマップを追加 cx.providers.の後に指定する
cx.add_basemap(ax,source=cx.providers.OpenTopoMap)

# 円の上にラベルを追加 黄色の文字で縁取りが黒
for idx, row in g_MLBAttendance.iterrows():
    plt.annotate(text=row['チーム'],xy=row['coords'],\
                 horizontalalignment='center',verticalalignment='center',color='Yellow',size=5,\
                 path_effects=[path_effects.withStroke(linewidth=1,foreground='Black')])
plt.show()

# 画像をjpeg形式で保存、保存場所はjupyter notebookのデフォルトディレクトリ
fig.savefig("MLBStadiumMap.jpg",bbox_inches='tight')

本質はコードの中、地図レイアウトの設定に書いた部分ですが、ベースマップも背景として追加すると地図が見やすくなります。位置からコードを作らなくても、便利なことに既存のマップサービスを使うことが出来ます。
その機能を使うのにライブラリcontextilyをimportしています。
ただしこれはpython3.7以降でないと動かないので注意してください。
ベースマップの種類にもいくつか面白いものがあるので、これについては、後日触れようと思います。

  • コードではジオコーディングで求めた球場の位置に円を描いています。

  • さらに、円の大きさと色でホームゲームの平均観客数を示しています。

  • またその位置には球団名(主に地名)を黄色で示し、右の枠外には凡例を置いています。

出来た地図がこれです。ブルージェイズはカナダのトロントにありますが、それも含めて地図ができています。
シアトル・マリナーズは1チームだけ抜群に遠いですね。移動の不利が大きそうですが、今年はスーパースターになりそうな予感のあるフリオ・ロドリゲス選手の活躍もあって好調で、観客数もいい感じのようです。

最終的な地図 MLBホームゲーム平均観客数 2022/8/19現在

まとめ

今回はpython上で、地名から位置座標を求める方法(ジオコーディング)と地図表示について主に紹介しました。コードのみでここまでできるのですがどうでしょう?
GoogleMapで良いとか、GISソフトを使えば良いと言うのはありますけれど、Webscapringと組み合わせてデータも更新しながらと考えると、なかなか面白いのではと思います。

ただ地図をよく見ると大都市(NYやLAなど)では複数の球団があって、円や文字が重なっていて、みづらいです。
また、拡大したり位置を動かしたりしたくなると思うのですが、このコードではあくまで画像を表示するだけで叶いません。
その辺が自由になるインタラクティブな地図表示方法というのもあるので、それはまた後日紹介します。

ここまでご覧いただきありがとうございました。
いつもは北海道に本拠地を置くNPOで、環境保全を主な題材としてGISやリモセンに関する仕事をしています。
コンサベーションGISコンソーシアムジャパン の活動もその1つです。

この分野の仕事や活動でなにかお困りのある方、ご相談ごとのある方など、是非コメントをお寄せいいただくか、上記WEBサイト掲載のメールアドレスまでメールをお寄せください。

最終的な地図 MLBホームゲーム平均観客数 2022/8/19現在 背景が夜景

個人的にはこのBasemapが気に入ってます。ナイターですね。

この記事が気に入ったらサポートをしてみませんか?